此文已由做者張威受權網易雲社區發佈。css
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。html
如今咱們有了一個可執行且不依賴於框架的應用程序,React 已經準備投入使用。react
視圖層由 presentational components 和 container components 組成。git
presentational components 關注事物的外觀,而 container components 則關注事物的工做方式。更多細節解釋請關注 Dan Abramov 的文章。github
讓咱們使用 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 庫以及如何加強咱們代碼的聲明性質的方法。app
表單接受用戶輸入並將其傳遞給 articleService 處理。此服務根據該輸入建立一個 Article,並將其添加到 ArticleStore 中以供 interested 組件使用它。全部這些邏輯都存儲在 submitForm 方法中。框架
『ArticleFormContainer.js』dom
// @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 的方法。
// @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 倉庫地址。
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 內容社交產品中的關鍵數據——得到良好反饋的用戶比例
【推薦】 致傳統企業朋友:不夠痛就別微服務,有坑 (1)
【推薦】 Android中Textview顯示Html,圖文混排,支持圖片點擊放大