国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

如何優(yōu)雅的設(shè)計 React 組件

Anchorer / 2629人閱讀

摘要:盡管現(xiàn)在的已不再那么流行,但的設(shè)計思想還是非常值得致敬和學(xué)習(xí)的,特別是的插件化。那么,如何解決我們回顧下的生命周期,父組件傳遞到子組件的的更新數(shù)據(jù)可以在中獲取。當(dāng)然,如何設(shè)計取決于你自己的項目,正所謂沒有最好的,

作者:曉冬
本文原創(chuàng),轉(zhuǎn)載請注明作者及出處

如今的 Web 前端已被 React、Vue 和 Angular 三分天下,一統(tǒng)江山十幾年的 jQuery 顯然已經(jīng)很難滿足現(xiàn)在的開發(fā)模式。那么,為什么大家會覺得 jQuery “過時了”呢?一來,文章《No JQuery! 原生 JavaScript 操作 DOM》就直截了當(dāng)?shù)母嬖V你,現(xiàn)在用原生 JavaScript 可以非常方便的操作 DOM 了。其次,jQuery 的便利性是建立在有一個基礎(chǔ) DOM 結(jié)構(gòu)的前提下的,看上去是符合了樣式、行為和結(jié)構(gòu)分離,但其實 DOM 結(jié)構(gòu)和 JavaScript 的代碼邏輯是耦合的,你的開發(fā)思路會不斷的在 DOM 結(jié)構(gòu)和 JavaScript 之間來回切換。

盡管現(xiàn)在的 jQuery 已不再那么流行,但 jQuery 的設(shè)計思想還是非常值得致敬和學(xué)習(xí)的,特別是 jQuery 的插件化。如果大家開發(fā)過 jQuery 插件的話,想必都會知道,一個插件要足夠靈活,需要有細顆粒度的參數(shù)化設(shè)計。一個靈活好用的 React 組件跟 jQuery 插件一樣,都離不開合理的屬性化(props)設(shè)計,但 React 組件的拆分和組合比起 jQuery 插件來說還是簡單的令人發(fā)指。

So! 接下來我們就以萬能的 TODO LIST 為例,一起來設(shè)計一款 React 的 TodoList 組件吧!

實現(xiàn)基本功能

TODO LIST 的功能想必我們應(yīng)該都比較了解,也就是 TODO 的添加、刪除、修改等等。本身的功能也比較簡單,為了避免示例的復(fù)雜度,顯示不同狀態(tài) TODO LIST 的導(dǎo)航(全部、已完成、未完成)的功能我們就不展開了。

約定目錄結(jié)構(gòu)

先假設(shè)我們已經(jīng)擁有一個可以運行 React 項目的腳手架(ha~ 因為我不是來教你如何搭建腳手架的),然后項目的源碼目錄 src/ 下可能是這樣的:

.
├── components
├── containers
│   └── App
│       ├── app.scss
│       └── index.js
├── index.html
└── index.js

我們先來簡單解釋下這個目錄設(shè)定。我們看到根目錄下的 index.js 文件是整個項目的入口模塊,入口模塊將會處理 DOM 的渲染和 React 組件的熱更新(react-hot-loader)等設(shè)置。然后,index.html 是頁面的 HTML 模版文件,這 2 個部分不是我們這次關(guān)心的重點,我們不再展開討論。

入口模塊 index.js 的代碼大概是這樣子的:

// import reset css, base css...

import React from "react";
import ReactDom from "react-dom";
import { AppContainer } from "react-hot-loader";
import App from "containers/App";

const render = (Component) => {
  ReactDom.render(
    
      
    ,
    document.getElementById("app")
  );
};

render(App);

if (module.hot) {
  module.hot.accept("containers/App", () => {
    let nextApp = require("containers/App").default;
    
    render(nextApp);
  });
}

接下來看 containers/ 目錄,它將放置我們的頁面容器組件,業(yè)務(wù)邏輯、數(shù)據(jù)處理等會在這一層做處理,containers/App 將作為我們的頁面主容器組件。作為通用組件,我們將它們放置于 components/ 目錄下。

基本的目錄結(jié)構(gòu)看起來已經(jīng)完成,接下來我們實現(xiàn)下主容器組件 containers/App

實現(xiàn)主容器

我們先來看下主容器組件 containers/App/index.js 最初的代碼實現(xiàn):

import React, { Component } from "react";
import styles from "./app.scss";

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      todos: []
    };
  }

  render() {
    return (
      

Todo List Demo

this.input = input} />
    {this.state.todos.map((todo, i) => (
  • this.handleStateChange(i)} > {todo.text}
  • ))}
); } handleAdd() { ... } handleRemove(index) { ... } handleStateChange(index) { ... } } export default App;

我們可以像上面這樣把所有的業(yè)務(wù)邏輯一股腦的塞進主容器中,但我們要考慮到主容器隨時會組裝其他的組件進來,將各種邏輯堆放在一起,到時候這個組件就會變得無比龐大,直到“無法收拾”。所以,我們得分離出一個獨立的 TodoList 組件。

分離組件 TodoList 組件

components/ 目錄下,我們新建一個 TodoList 文件夾以及相關(guān)文件:

.
├── components
+│   └── TodoList
+│       ├── index.js
+│       └── todo-list.scss
├── containers
│   └── App
│       ├── app.scss
│       └── index.js
...

然后我們將 containers/App/index.js 下跟 TodoList 組件相關(guān)的功能抽離到 components/TodoList/index.js 中:

...
import styles from "./todo-list.scss";

export default class TodoList extends Component {
  ...
  
  render() {
    return (
      
-
+
this.input = input} />
-
+
-
    +
      {this.state.todos.map((todo, i) => (
    • this.handleStateChange(i)} > {todo.text}
    • ))}
); } ... }

有沒有注意到上面 render 方法中的 className,我們省去了 todo-list* 前綴,由于我們用的是 CSS MODULES,所以當(dāng)我們分離組件后,原先在主容器中定義的 todo-list* 前綴的 className ,可以很容易通過 webpack 的配置來實現(xiàn):

...
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /.s?css/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              modules: true,
              localIdentName: "[name]--[local]-[hash:base64:5]"
            }
          },
          ...
        ]
      }
    ]  
  }
  ...
};

我們再來看下該組件的代碼輸出后的結(jié)果:

...
...
    ...

從上面 webpack 的配置和輸出的 HTML 中可以看到,className 的命名空間問題可以通過語義化 *.scss 文件名的方式來實現(xiàn),比如 TodoList 的樣式文件 todo-list.scss。這樣一來,省去了我們定義組件 className 的命名空間帶來的煩惱,從而只需要從組件內(nèi)部的結(jié)構(gòu)下手。

回到正題,我們再來看下分離 TodoList 組件后的 containers/App/index.js

import TodoList from "components/TodoList";
...

class App extends Component {
  render() {
    return (
      

Todo List Demo

); } } export default App;
抽離通用組件

作為一個項目,當(dāng)前的 TodoList 組件包含了太多的子元素,如:input、button 等。為了讓組件“一次編寫,隨處使用”的原則,我們可以進一步拆分 TodoList 組件以滿足其他組件的使用。

但是,如何拆分組件才是最合理的呢?我覺得這個問題沒有最好的答案,但我們可以從幾個方面進行思考:可封裝性、可重用性和靈活性。比如拿 h1 元素來講,你可以封裝成一個 Title 組件,然后這樣 </b> 使用,又或者可以這樣 <b><Title>{title} 來使用。但你有沒有發(fā)現(xiàn),這樣實現(xiàn)的 Title 組件并沒有起到簡化和封裝的作用,反而增加了使用的復(fù)雜度,對于 HTML 來講,h1 本身也是一個組件,所以我們拆分組件也是需要掌握一個度的。

好,我們先拿 input 和 button 下手,在 components/ 目錄下新建 2 個 ButtonInput 組件:

.
├── components
+│   ├── Button
+│   │   ├── button.scss
+│   │   └── index.js
+│   ├── Input
+│   │   ├── index.js
+│   │   └── input.scss
│   └── TodoList
│       ├── index.js
│       └── todo-list.scss
...

Button/index.js 的代碼:

...
export default class Button extends Component {
  render() {
    const { className, children, onClick } = this.props;

    return (
      
    );
  }
}

Input/index.js 的代碼:

...
export default class Input extends Component {
  render() {
    const { className, value, inputRef } = this.props;

    return (
      
    );
  }
}

由于這 2 個組件自身不涉及任何業(yè)務(wù)邏輯,應(yīng)該屬于純渲染組件(木偶組件),我們可以使用 React 輕量的無狀態(tài)組件的方式來聲明:

...
const Button = ({ className, children, onClick }) => (
  
);

是不是覺得酷炫很多!

另外,從 Input 組件的示例代碼中看到,我們使用了非受控組件,這里是為了降低示例代碼的復(fù)雜度而特意為之,大家可以根據(jù)自己的實際情況來決定是否需要設(shè)計成受控組件。一般情況下,如果不需要獲取實時輸入值的話,我覺得使用非受控組件應(yīng)該夠用了。

我們再回到上面的 TodoList 組件,將之前分離的子組件 ButtonInput 組裝進來。

...
import Button from "components/Button";
import Input from "components/Input";
...

export default class TodoList extends Component {
  render() {
    return (
      
this.input = input} />
...
); } } ...
拆分子組件

然后繼續(xù)接著看 TodoList 的 items 部分,我們注意到這部分包含了較多的渲染邏輯在 render 中,導(dǎo)致我們需要浪費對這段代碼與上下文之間會有過多的思考,所以,我們何不把它抽離出去:

...

export default class TodoList extends Component {
  render() {
    return (
      
...
{this.renderItems()}
); } renderItems() { return (
    {this.state.todos.map((todo, i) => (
  • ...
  • ))}
); } ... }

上面的代碼看似降低了 render 的復(fù)雜度,但仍然沒有讓 TodoList 減少負(fù)擔(dān)。既然我們要把這部分邏輯分離出去,我們何不創(chuàng)建一個 Todos 組件,把這部分邏輯拆分出去呢?so,我們以“就近聲明”的原則在 components/TodoList/ 目錄下創(chuàng)建一個子目錄 components/TodoList/components/ 來存放 TodoList 的子組件 。why?因為我覺得 組件 TodosTodoList 有緊密的父子關(guān)系,且跟其他組件間也不太會有任何交互,也可以認(rèn)為它是 TodoList 私有的。

然后我們預(yù)覽下現(xiàn)在的目錄結(jié)構(gòu):

.
├── components
│   ...
│   └── TodoList
+│       ├── components
+│       │   └── Todos
+│       │       ├── index.js
+│       │       └── todos.scss
│       ├── index.js
│       └── todo-list.scss

Todos/index.js 的代碼:

...
const Todos = ({ data: todos, onStateChange, onRemove }) => (
  
    {todos.map((todo, i) => (
  • onStateChange(i)} > {todo.text}
  • ))}
); ...

再看拆分后的 TodoList/index.js

render() {
  return (
    
...
this.handleStateChange(index)} onRemove={(index) => this.handleRemove(index)} />
); }
增強子組件

到目前為止,大體上的功能已經(jīng)搞定,子組件看上去拆分的也算合理,這樣就可以很容易的增強某個子組件的功能了。就拿 Todos 來說,在新增了一個 TODO 后,假如我們并沒有完成這個 TODO,而我們又希望可以修改它的內(nèi)容了。ha~不要著急,要不我們再拆分下這個 Todos,比如增加一個 Todo 組件:

.
├── components
│   ...
│   └── TodoList
│       ├── components
+│       │   ├── Todo
+│       │   │   ├── index.js
+│       │   │   └── todo.scss
│       │   └── Todos
│       │       ├── index.js
│       │       └── todos.scss
│       ├── index.js
│       └── todo-list.scss

先看下 Todos 組件在抽離了 Todo 后的樣子:

...
import Todo from "../Todo";
...

const Todos = ({ data: todos, onStateChange, onRemove }) => (
  
    {todos.map((todo, i) => (
  • onStateChange(i)} />
  • ))}
); export default Todos;

我們先不關(guān)心 Todo 內(nèi)是何如實現(xiàn)的,就如我們上面說到的那樣,我們需要對這個 Todo 增加一個可編輯的功能,從單純的屬性配置入手,我們只需要給它增加一個 editable 的屬性:

 onStateChange(i)}
/>

然后,我們再思考下,在 Todo 組件的內(nèi)部,我們需要重新組織一些功能邏輯:

根據(jù)傳入的 editable 屬性來判斷是否需要顯示編輯按鈕

根據(jù)組件內(nèi)部的編輯狀態(tài),是顯示文本輸入框還是文本內(nèi)容

點擊“更新”按鈕后,需要通知父組件更新數(shù)據(jù)列表

我們先來實現(xiàn)下 Todo 的第一個功能點:

render() {
  const { completed, text, editable, onClick } = this.props;

  return (
    
      
        {text}
      
      {editable && 
        
      }
    
  );
}

顯然實現(xiàn)這一步似乎沒什么 luan 用,我們還需要點擊 Edit 按鈕后能顯示 Input 組件,使內(nèi)容可修改。所以,簡單的傳遞屬性似乎無法滿足該組件的功能,我們還需要一個內(nèi)部狀態(tài)來管理組件是否處于編輯中:

render() {
  const { completed, text, editable, onStateChange } = this.props,
    { editing } = this.state;

  return (
    
      {editing ? 
         this.input = input}
        /> :
        
          {text}
        
      }
      {editable && 
        
      }
    
  );
}

最后,Todo 組件在點擊 Update 按鈕后需要通知父組件更新數(shù)據(jù):

handleEdit() {
  const { text, onUpdate } = this.props;
  let { editing } = this.state;

  editing = !editing;

  this.setState({ editing });

  if (!editing && this.input.value !== text) {
    onUpdate(this.input.value);
  }
}

需要注意的是,我們傳遞的是更新后的內(nèi)容,在數(shù)據(jù)沒有任何變化的情況下通知父組件是毫無意義的。

我們再回過頭來修改下 Todos 組件對 Todo 的調(diào)用。先增加一個由 TodoList 組件傳遞下來的回調(diào)屬性 onUpdate,同時修改 onClickonStateChange,因為這時的 Todo 已不僅僅只有單個點擊事件了,需要定義不同狀態(tài)變更時的事件回調(diào):

 onStateChange(i)}
+ onStateChange={() => onStateChange(i)}
+ onUpdate={(value) => onUpdate(i, value)}
/>

而最終我們又在 TodoList 組件中,增加 Todo 在數(shù)據(jù)更新后的業(yè)務(wù)邏輯。

TodoList 組件的 render 方法內(nèi)的部分示例代碼:

 this.handleUpdate(index, value)}
  onStateChange={(index) => this.handleStateChange(index)}
  onRemove={(index) => this.handleRemove(index)}
/>

TodoList 組件的 handleUpdate 方法的示例代碼:

handleUpdate(index, value) {
  let todos = [...this.state.todos];
  const target = todos[index];

  todos = [
    ...todos.slice(0, index),
    {
      text: value,
      completed: target.completed
    },
    ...todos.slice(index + 1)
  ];

  this.setState({ todos });
}
組件數(shù)據(jù)管理

既然 TodoList 是一個組件,初始狀態(tài) this.state.todos 就有可能從外部傳入。對于組件內(nèi)部,我們不應(yīng)該過多的關(guān)心這些數(shù)據(jù)從何而來(可能通過父容器直接 Ajax 調(diào)用后返回的數(shù)據(jù),或者 Redux、MobX 等狀態(tài)管理器獲取的數(shù)據(jù)),我覺得組件的數(shù)據(jù)屬性的設(shè)計可以從以下 3 個方面來考慮:

在沒有初始數(shù)據(jù)傳入時應(yīng)該提供一個默認(rèn)值

一旦數(shù)據(jù)在組件內(nèi)部被更新后應(yīng)該及時的通知父組件

當(dāng)有新的數(shù)據(jù)(從后端 API 請求的)傳入組件后,應(yīng)該重新更新組件內(nèi)部狀態(tài)

根據(jù)這幾點,我們可以對 TodoList 再做一番改造。

首先,對 TodoList 增加一個 todos 的默認(rèn)數(shù)據(jù)屬性,使父組件在沒有傳入有效屬性值時也不會影響該組件的使用:

export default class TodoList extends Component {
  constructor(props) {
    super(props);

    this.state = {
      todos: props.todos
    };
  }
  ...
}

TodoList.defaultProps = {
  todos: []
};

然后,再新增一個內(nèi)部方法 this.update 和一個組件的更新事件回調(diào)屬性 onUpdate,當(dāng)數(shù)據(jù)狀態(tài)更新時可以及時的通知父組件:

export default class TodoList extends Component {
  ...
  handleAdd() {
    ...
    this.update(todos);
  }

  handleUpdate(index, value) {
    ...
    this.update(todos);
  }

  handleRemove(index) {
    ...
    this.update(todos);
  }

  handleStateChange(index) {
    ...
    this.update(todos);
  }

  update(todos) {
    const { onUpdate } = this.props;

    this.setState({ todos });
    onUpdate && onUpdate(todos);
  }
}

這就完事兒了?No! No! No! 因為 this.state.todos 的初始狀態(tài)是由外部 this.props 傳入的,假如父組件重新更新了數(shù)據(jù),會導(dǎo)致子組件的數(shù)據(jù)和父組件不同步。那么,如何解決?

我們回顧下 React 的生命周期,父組件傳遞到子組件的 props 的更新數(shù)據(jù)可以在 componentWillReceiveProps 中獲取。所以我們有必要在這里重新更新下 TodoList 的數(shù)據(jù),哦!千萬別忘了判斷傳入的 todos 和當(dāng)前的數(shù)據(jù)是否一致,因為,當(dāng)任何傳入的 props 更新時都會導(dǎo)致 componentWillReceiveProps 的觸發(fā)。

componentWillReceiveProps(nextProps) {
  const nextTodos = nextProps.todos;

  if (Array.isArray(nextTodos) && !_.isEqual(this.state.todos, nextTodos)) {
    this.setState({ todos: nextTodos });
  }
}

注意代碼中的 _.isEqual,該方法是 Lodash 中非常實用的一個函數(shù),我經(jīng)常拿來在這種場景下使用。

結(jié)尾

由于本人對 React 的了解有限,以上示例中的方案可能不一定最合適,但你也看到了 TodoList 組件,既可以是包含多個不同功能邏輯的大組件,也可以拆分為獨立、靈巧的小組件,我覺得我們只需要掌握一個度。當(dāng)然,如何設(shè)計取決于你自己的項目,正所謂:沒有最好的,只有更合適的。還是希望本篇文章能給你帶來些許的小收獲。

iKcamp官網(wǎng):http://www.ikcamp.com

訪問官網(wǎng)更快閱讀全部免費分享課程:《iKcamp出品|全網(wǎng)最新|微信小程序|基于最新版1.0開發(fā)者工具之初中級培訓(xùn)教程分享》。
包含:文章、視頻、源代碼

iKcamp原創(chuàng)新書《移動Web前端高效開發(fā)實戰(zhàn)》已在亞馬遜、京東、當(dāng)當(dāng)開售。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/89657.html

相關(guān)文章

  • React 應(yīng)用設(shè)計之道 - curry 化妙用

    摘要:右側(cè)展現(xiàn)對應(yīng)產(chǎn)品。我們使用命名為的對象表示過濾條件信息,如下此數(shù)據(jù)需要在組件中進行維護。因為組件的子組件和都將依賴這項數(shù)據(jù)狀態(tài)。化應(yīng)用再回到之前的場景,我們設(shè)計化函數(shù),進一步可以簡化為對于的偏應(yīng)用即上面提到的相信大家已經(jīng)理解了這么做的好處。 showImg(https://segmentfault.com/img/remote/1460000014458612?w=1240&h=663...

    sewerganger 評論0 收藏0
  • React 應(yīng)用設(shè)計之道 - curry 化妙用

    摘要:右側(cè)展現(xiàn)對應(yīng)產(chǎn)品。我們使用命名為的對象表示過濾條件信息,如下此數(shù)據(jù)需要在組件中進行維護。因為組件的子組件和都將依賴這項數(shù)據(jù)狀態(tài)。化應(yīng)用再回到之前的場景,我們設(shè)計化函數(shù),進一步可以簡化為對于的偏應(yīng)用即上面提到的相信大家已經(jīng)理解了這么做的好處。 showImg(https://segmentfault.com/img/remote/1460000014458612?w=1240&h=663...

    LinkedME2016 評論0 收藏0
  • 玩轉(zhuǎn) React(三)- JavaScript代碼里寫HTML一樣可以很優(yōu)雅

    摘要:但是隨著程序邏輯越來越復(fù)雜,業(yè)務(wù)邏輯代碼跟代碼混到一起就變得越來越難以維護,所以就有了開發(fā)模式。其實只是給加了點糖上面這種在中寫類似代碼的語法被稱為。你可以理解為擴展版的。尤其是對一些相對還比較流行的框架或技術(shù),更是如此。 這是《玩轉(zhuǎn) React》系列的第三篇,看到本篇的標(biāo)題,了解過 React 的同學(xué)可能已經(jīng)大致猜到我要講什么了,本篇中要講的內(nèi)容對于剛接觸 React 的同學(xué)來說,可...

    android_c 評論0 收藏0
  • 玩轉(zhuǎn) React(三)- JavaScript代碼里寫HTML一樣可以很優(yōu)雅

    摘要:但是隨著程序邏輯越來越復(fù)雜,業(yè)務(wù)邏輯代碼跟代碼混到一起就變得越來越難以維護,所以就有了開發(fā)模式。其實只是給加了點糖上面這種在中寫類似代碼的語法被稱為。你可以理解為擴展版的。尤其是對一些相對還比較流行的框架或技術(shù),更是如此。 這是《玩轉(zhuǎn) React》系列的第三篇,看到本篇的標(biāo)題,了解過 React 的同學(xué)可能已經(jīng)大致猜到我要講什么了,本篇中要講的內(nèi)容對于剛接觸 React 的同學(xué)來說,可...

    elarity 評論0 收藏0

發(fā)表評論

0條評論

Anchorer

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<