此文已由作者张威授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

视图层

现在我们有了一个可执行且不依赖于框架的应用程序,React 已经准备投入使用。

视图层由 presentational components 和 container components 组成。

presentational components 关注事物的外观,而 container components 则关注事物的工作方式。更多细节解释请关注 Dan Abramov 的文章

让我们使用 ArticleFormContainer 和 ArticleListContainer 开始构建 App 组件。

// @flowimport React, {Component} from 'react';import './App.css';import {ArticleFormContainer} from "./components/ArticleFormContainer";import {ArticleListContainer} from "./components/ArticleListContainer";

type Props = {};class App extends Component<Props> {
  render() {    return (
      <div className="App">
        <ArticleFormContainer/>
        <ArticleListContainer/>
      </div>
    );
  }
}export default App;

接下来,我们来创建 ArticleFormContainer。React 或者 Angular 都不重要,表单有些复杂。

查看 Ramda 库以及如何增强我们代码的声明性质的方法。

表单接受用户输入并将其传递给 articleService 处理。此服务根据该输入创建一个 Article,并将其添加到 ArticleStore 中以供 interested 组件使用它。所有这些逻辑都存储在 submitForm 方法中。

『ArticleFormContainer.js』

// @flowimport React, {Component} from 'react';import * as R from 'ramda';import type {ArticleService} from "../domain/ArticleService";import type {ArticleStore} from "../store/ArticleStore";import {articleService} from "../domain/ArticleService";import {articleStore} from "../store/ArticleStore";import {ArticleFormComponent} from "./ArticleFormComponent";

type Props = {};

type FormField = {  value: string;  valid: boolean;
} export type FormData = {  articleTitle: FormField;  articleAuthor: FormField;
}; export class ArticleFormContainer extends Component<Props, FormData> {  articleStore: ArticleStore;  articleService: ArticleService;   constructor(props: Props) {    super(props);    this.state = {      articleTitle: {        value: '',        valid: true
      },      articleAuthor: {        value: '',        valid: true
      }
    };    this.articleStore = articleStore;    this.articleService = articleService;
  }   changeArticleTitle(event: Event) {    this.setState(
      R.assocPath(
        ['articleTitle', 'value'],
        R.path(['target', 'value'], event)
      )
    );
  }   changeArticleAuthor(event: Event) {    this.setState(
      R.assocPath(
        ['articleAuthor', 'value'],
        R.path(['target', 'value'], event)
      )
    );
  }   submitForm(event: Event) {
    const articleTitle = R.path(['target', 'articleTitle', 'value'], event);
    const articleAuthor = R.path(['target', 'articleAuthor', 'value'], event);     const isTitleValid = this.articleService.isTitleValid(articleTitle);
    const isAuthorValid = this.articleService.isAuthorValid(articleAuthor);    if (isTitleValid && isAuthorValid) {
      const newArticle = this.articleService.createArticle({        title: articleTitle,        author: articleAuthor
      });      if (newArticle) {        this.articleStore.addArticle(newArticle);
      }      this.clearForm();
    } else {      this.markInvalid(isTitleValid, isAuthorValid);
    }
  };   clearForm() {    this.setState((state) => {      return R.pipe(
        R.assocPath(['articleTitle', 'valid'], true),
        R.assocPath(['articleTitle', 'value'], ''),
        R.assocPath(['articleAuthor', 'valid'], true),
        R.assocPath(['articleAuthor', 'value'], '')
      )(state);
    });
  }   markInvalid(isTitleValid: boolean, isAuthorValid: boolean) {    this.setState((state) => {      return R.pipe(
        R.assocPath(['articleTitle', 'valid'], isTitleValid),
        R.assocPath(['articleAuthor', 'valid'], isAuthorValid)
      )(state);
    });
  }   render() {    return (
      <ArticleFormComponent
        formData={this.state}
        submitForm={this.submitForm.bind(this)}
        changeArticleTitle={(event) => this.changeArticleTitle(event)}
        changeArticleAuthor={(event) => this.changeArticleAuthor(event)}
      />
    )
  }
}

这里注意 ArticleFormContainer,presentational component,返回用户看到的真实表单。该组件显示容器传递的数据,并抛出 changeArticleTitle、 changeArticleAuthor 和 submitForm 的方法。

ArticleFormComponent.js

// @flow
import React from 'react'; import type {FormData} from './ArticleFormContainer'; type Props = {
  formData: FormData;
  changeArticleTitle: Function;
  changeArticleAuthor: Function;
  submitForm: Function;
} export const ArticleFormComponent = (props: Props) => {
  const {
    formData,
    changeArticleTitle,
    changeArticleAuthor,
    submitForm
  } = props;   const onSubmit = (submitHandler) => (event) => {
    event.preventDefault();
    submitHandler(event);
  };   return (    <form
      noValidate
      onSubmit={onSubmit(submitForm)}
    >
      <div>
        <label htmlFor="article-title">Title</label>
        <input
          type="text"
          id="article-title"
          name="articleTitle"
          autoComplete="off"
          value={formData.articleTitle.value}
          onChange={changeArticleTitle}
        />
        {!formData.articleTitle.valid && (<p>Please fill in the title</p>)}      </div>
      <div>
        <label htmlFor="article-author">Author</label>
        <input
          type="text"
          id="article-author"
          name="articleAuthor"
          autoComplete="off"
          value={formData.articleAuthor.value}
          onChange={changeArticleAuthor}
        />
        {!formData.articleAuthor.valid && (<p>Please fill in the author</p>)}      </div>
      <button
        type="submit"
        value="Submit"
      >
        Create article      </button>
    </form>
  )
};

现在我们有了创建文章的表单,下面就陈列他们吧。ArticleListContainer 订阅了 ArticleStore,获取所有的文章并展示在 ArticleListComponent 中。

『ArticleListContainer.js』

// @flowimport * as React from 'react'import type {Article} from "../domain/Article";import type {ArticleStore} from "../store/ArticleStore";import {articleStore} from "../store/ArticleStore";import {ArticleListComponent} from "./ArticleListComponent";

type State = {  articles: Article[]
} type Props = {}; export class ArticleListContainer extends React.Component<Props, State> {  subscriber: Function;  articleStore: ArticleStore;   constructor(props: Props) {    super(props);    this.articleStore = articleStore;    this.state = {      articles: []
    };    this.subscriber = this.articleStore.subscribe((articles: Article[]) => {      this.setState({articles});
    });
  }   componentWillUnmount() {    this.articleStore.unsubscribe(this.subscriber);
  }   render() {    return <ArticleListComponent {...this.state}/>;
  }
}

ArticleListComponent 是一个 presentational component,他通过 props 接收文章,并展示组件 ArticleContainer。

『ArticleListComponent.js』

// @flowimport React from 'react';import type {Article} from "../domain/Article";import {ArticleContainer} from "./ArticleContainer";

type Props = {  articles: Article[]
}export const ArticleListComponent = (props: Props) => {  const {articles} = props;  return (
    <div>
      {
        articles.map((article: Article, index) => (
          <ArticleContainer
            article={article}
            key={index}
          />
        ))
      }
    </div>
  )
};

ArticleContainer 传递文章数据到表现层的 ArticleComponent,同时实现 likeArticle 和 removeArticle 这两个方法。

likeArticle 方法负责更新文章的收藏数,通过将现存的文章替换成更新后的副本。

removeArticle 方法负责从 store 中删除制定文章。

『ArticleContainer.js』

// @flowimport React, {Component} from 'react';import type {Article} from "../domain/Article";import type {ArticleService} from "../domain/ArticleService";import type {ArticleStore} from "../store/ArticleStore";import {articleService} from "../domain/ArticleService";import {articleStore} from "../store/ArticleStore";import {ArticleComponent} from "./ArticleComponent";

type Props = {  article: Article;
}; export class ArticleContainer extends Component<Props> {  articleStore: ArticleStore;  articleService: ArticleService;   constructor(props: Props) {    super(props);    this.articleStore = articleStore;    this.articleService = articleService;
  }   likeArticle(article: Article) {
    const updatedArticle = this.articleService.updateLikes(article, article.likes + 1);    this.articleStore.updateArticle(updatedArticle);
  }   removeArticle(article: Article) {    this.articleStore.removeArticle(article);
  }   render() {    return (
      <div>
        <ArticleComponent
          article={this.props.article}
          likeArticle={(article: Article) => this.likeArticle(article)}
          deleteArticle={(article: Article) => this.removeArticle(article)}
        />
      </div>
    )
  }
}

ArticleContainer 负责将文章的数据传递给负责展示的 ArticleComponent,同时负责当 「收藏」或「删除」按钮被点击时在响应的回调中通知 container component。

还记得那个作者名要大写的无厘头需求吗?

ArticleComponent 在应用程序层调用 ArticleUiService,将一个状态从其原始值(没有大写规律的字符串)转换成一个所需的大写字符串。

『ArticleComponent.js』

// @flowimport React from 'react';import type {Article} from "../domain/Article";import * as articleUiService from "../services/ArticleUiService";

type Props = {  article: Article;  likeArticle: Function;  deleteArticle: Function;
}export const ArticleComponent = (props: Props) => {  const {
    article,
    likeArticle,
    deleteArticle
  } = props;  return (
    <div>
      <h3>{article.title}</h3>
      <p>{articleUiService.displayAuthor(article.author)}</p>
      <p>{article.likes}</p>
      <button
        type="button"
        onClick={() => likeArticle(article)}
      >
        Like
      </button>
      <button
        type="button"
        onClick={() => deleteArticle(article)}
      >
        Delete
      </button>
    </div>
  );
};

干得漂亮!

我们现在有一个功能完备的 React 应用程序和一个鲁棒的、定义清晰的架构。任何新晋成员都可以通过阅读这篇文章学会如何顺利的进展我们的工作。:)

你可以在这里查看我们最终实现的应用程序,同时奉上 GitHub 仓库地址

免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 Docker 的优势

[译] 关于 SPA,你需要掌握的 4 层 (2)的更多相关文章

  1. [译] 关于 SPA,你需要掌握的 4 层 (1)

    此文已由作者张威授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 我们从头来构建一个 React 的应用程序,探究领域.存储.应用服务和视图这四层 每个成功的项目都需要一个清晰 ...

  2. 【译】为什么BERT有3个嵌入层,它们都是如何实现的

    目录 引言 概览 Token Embeddings 作用 实现 Segment Embeddings 作用 实现 Position Embeddings 作用 实现 合成表示 结论 参考文献 本文翻译 ...

  3. 有道词典中的OCR功能:第三方库的变化

    之前有点好奇有道词典中的OCR功能,具体来说就是强力取词功能.我知道的最有名的OCR库是tesseract,这个库是惠普在早些年前开源的. 在用python做爬虫处理验证码的时候,就会用到这个库,对应 ...

  4. 第一章:关于Ehcache

    PDF官方文档:http://www.ehcache.org/generated/2.10.4/pdf/About_Ehcache.pdf 1.什么是Ehcache? Ehcache是一种开源的基于标 ...

  5. Ehcache概念篇

    前言 缓存用于提供性能和减轻数据库负荷,本文在缓存概念的基础上对Ehcache进行叙述,经实践发现3.x版本高可用性不好实现,所以我采用2.x版本. Ehcache是开源.基于缓存标准.基于java的 ...

  6. 《Effective Java》第9章 异常

    第58条:对可恢复的情况使用受检异常,对编程错误使用运行时异常 Java程序设计语言提供了三种可抛出结构(throwable) ;受检的异常(checked exception)运行时异常(run-t ...

  7. Java 异常规范

    1. 只针对异常情况使用异常,不要用异常来控制流程 try { int i = 0; while (true) { range[i++].doSomething(); } } catch (Array ...

  8. Effective.Java第67-77条(异常相关)

    67.  明智审慎地进行优化 有三条优化的格言是每个人都应该知道的: (1)比起其他任何单一的原因(包括盲目的愚钝),很多计算上的过失都被归咎于效率(不一定能实现) (2)不要去计算效率上的一些小小的 ...

  9. [译]ABP框架使用AngularJs,ASP.NET MVC,Web API和EntityFramework构建N层架构的SPA应用程序

    本文转自:http://www.skcode.cn/archives/281 本文演示ABP框架如何使用AngularJs,ASP.NET MVC,Web API 和EntityFramework构建 ...

随机推荐

  1. thinkphp线上自动加载异常与修复

    项目遇到一个奇怪的问题,本地代码正常,服务器上却不正常. 经过测试,应该是自动加载出了问题,尝试了各种方法, 1.手动加载,发现好麻烦,没完没了. 2.自己写自动加载,写不出来,尴尬. 3.修改配置, ...

  2. Xdebug日志文件不显示

    Xdebug是一个很强大的调试php的软件,安装也很简单. 1.php_xdebug.dll 放入php目录下的ext文件中 2.php.ini中开启 [Xdebug] extension = &qu ...

  3. Maria数据库

    项目上要进行数据库选型,业务上来讲,数据是非常结构化的数据,使用传统关系数据库更适合:另外项目采用微服务框架,每个服务的数据库应该尽可能轻量级, 最后考虑Maria数据库. MariaDB简介: Ma ...

  4. python中的异常处理机制

    python中的异常处理 1.什么是异常 异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异 ...

  5. VC6编写的Dll调试方法

    Dll工程运行时指定调用exe程序. 关键!!往往被忽略:exe中也一定要指向此调用dll,如果指向不对,什么效果也没有!

  6. 第十二章 MySQL触发器(待续)

    ······

  7. MIS系统部署方案

  8. K12协同开发在做常见问题时候遇到的问题

    一.在做常见问题的时候遇到的问题 在后端处理数据的时候是通过serialize来实现的,从数据库中查出自己想要的数据,直接返回数据. 在前端发送ajax请求获取数据并且在页面上以好看的形式渲染. 1. ...

  9. Java面向对象-面向对象编程之基本概念

    面向对象这个概念,每本书上的说法定义很多. 我自己根据我的经验,自己归档总结了下, 所谓面向对象,就是 以基于对象的思维去分析和解决问题,万物皆对象: 面向对象经常和面向过程放一起讨论: 这里举例, ...

  10. mysql 添加字段、删除字段、调整字段顺序

    用过MySQL的朋友,可能都在使用phpMyAdmin,我从2003年开始使用,感觉那东西适合远程mysql管理,并 不适合单机.单数据库的管理操作,特别是开发使用. 给家推荐一个软件管理mysql数 ...