React組件「設計模式」快速指南

Github: github.com/gaoljie/rea…html

函數組件(Functional Component)

函數組件是純 UI 組件,也稱做傻瓜組件, 或者無狀態組件。渲染所須要的數據只經過 props 傳入, 不須要用 class 的方式來建立 React 組件, 也不須要用到 this 關鍵字,或者用到 statereact

函數組件具備如下優勢git

  • 可讀性好
  • 邏輯簡單
  • 測試簡單
  • 代碼量少
  • 容易複用
  • 解耦
const Hello = props => <div>Hello {props.name}</div>;

const App = props => <Hello name={"world"} />;
複製代碼

什麼狀況下不使用函數組件? 若是你須要用到 react 生命週期, 須要用到 state, 函數組件就沒辦法知足要求了。 可是新的 Hook 特性出來以後又大大提高了函數組件的應用範圍, 因此沒有最佳的設計模式,都是要因地制宜。github

Render Props

給某個組件經過 props 傳遞一個函數,而且函數會返回一個 React 組件,這就是 render props.編程

const Hello = props => <div>{props.render({ name: "World" })}</div>;

const App = props => <Hello render={props => <h1>Hello {props.name}</h1>} />;
複製代碼

你也能夠把函數放在組件 tag 的中間,組件能夠經過props.children 獲取設計模式

const Hello = props => <div>{props.children({ name: "World" })}</div>;

const App = props => <Hello>{props => <h1>Hello {props.name}</h1>}</Hello>;

//複用
const App2 = props => <Hello>{props => <h1>Hey {props.name}</h1>}</Hello>;
複製代碼

render props 提升了組件的複用性和靈活性, 相比組件直接渲染特定模板,經過 render props,父組件自定義須要的模板,子組件只要調用父組件提供的函數, 傳入須要的數據就能夠了。數組

高階組件(HOC)

高階組件是一個接受 Component 並返回新的 Component 的函數。之因此叫高階組件是由於它和函數式編程中的高階函數相似, 一個接受函數爲參數, 或者返回值爲函數的函數便稱做高階函數.app

const Name = props => <span>{props.children}</span>;

const reverse = Component => {
  return ({ children, ...props }) => (
    <Component {...props}>
      {children
        .split("")
        .reverse()
        .join("")}
    </Component>
  );
};

const ReversedName = reverse(Name);

const App = props => <ReversedName>hello world</ReversedName>;
複製代碼

reverse 函數接受一個 Component 參數,返回一個能夠 reverse 內容的新的函數式組件。這個高階組件封裝了 reverse 方法,之後每次須要 reverse 某些組件的內容就不必重複寫一下步驟:ide

const Name = props => <span>{props.children}</span>;

const App = props => (
  <Name>
    {"hello world"
      .split("")
      .reverse()
      .join("")}
  </Name>
);
複製代碼

組合組件(Compound Components)

組合組件設計模式通常應用在一些共享組件上。 如 <select><option>, <Tab><TabItem>等,經過組合組件,使用者只須要傳遞子組件,子組件所須要的props在父組件會封裝好,引用子組件的時候就不必傳遞全部props了。 好比下面的例子,每一個 ListItem 須要一個index 參數來顯示第幾項, 可使用下面的方式渲染函數式編程

const List = ({ children }) => <ul>{children}</ul>;

const ListItem = ({ children, index }) => (
  <li>
    {index} {children}
  </li>
);

const App = props => (
  <List>
    <ListItem index={1}>apple</ListItem>
    <ListItem index={2}>banana</ListItem>
  </List>
);
複製代碼

這種方式存在一些問題, 每次新增長一個列表項都要手動傳一個index值,若是之後要加其餘的屬性, 就須要每一項都修改,組合組件能夠避免上述的缺陷。

const List = ({children}) => (
    <ul>
        {React.Children.map(children, (child, index) => React.cloneElement(child, {
            index: index
        }))}
    </ul>
)

const ListItem = ({children, index}) => (
    <li>{index} {children}</li>
)

<List>
      <ListItem>apple</ListItem>
      <ListItem>banana</ListItem>
</List>
複製代碼

若是把 ListItem 經過static方式放在 List 組件裏面,更具語義化。

class List extends Component {
  static Item = ({ children, index }) => (
    <li> {index} {children} </li>
  );

  render() {
    return (
      <ul> {React.Children.map(this.props.children, (child, index) => React.cloneElement(child, { index: index }) )} </ul>
    );
  }
}

const App = props => (
  <List>
    <List.Item>apple</List.Item>
    <List.Item>banana</List.Item>
  </List>
);
複製代碼

提供者模式(Provider Pattern)

提供者模式能夠解決非父子組件下的信息傳遞問題, 或者組件層級太深須要層層傳遞的問題

const Child = ({ lastName }) => <p>{lastName}</p>;

const Mother = ({ lastName }) => <Child lastName={lastName} />;

const GrandMother = ({ lastName }) => <Mother lastName={lastName} />;

const App = props => <GrandMother lastName={"Kieffer"} />;
複製代碼

上面的例子lastName須要在每一個組件都傳遞一次,提供者模式就能夠避免這種 Prop Drilling 的寫法

const FamilyContext = React.createContext({});
const FamilyProvider = FamilyContext.Provider;
const FamilyConsumer = FamilyContext.Consumer;

const Child = ({ lastName }) => (
  <FamilyConsumer>{context => <p>{context}</p>}</FamilyConsumer>
);

const Mother = () => <Child />;

const GrandMother = () => <Mother />;

const App = props => (
  <FamilyProvider value={"Kieffer"}> <GrandMother /> </FamilyProvider>
);
複製代碼

State Reducer

State Reducer可讓父組件控制子組件state。render props 能夠控制子組件的UI是如何渲染的,state reducer則能夠控制子組件的state.

下面的例子,經過傳入state reducer方法,父組件能夠控制子組件最多隻點擊4次。

class Counter extends Component{
  state = {
    count: 0
  }

  setInternalState = (stateOrFunc, callback) => {
    this.setState(state => {
      const changedState = typeof stateOrFunc === 'function'
          ? stateOrFunc(state)
          : stateOrFunc

      const reducedState = this.props.stateReducer(state, changedState) || {}

      // return null when reducedState is an empty object, prevent rerendering
      return Object.keys(reducedState).length > 0
          ? reducedState
          : null
    }, callback)
  }

  addCount = () => this.setInternalState(state => ({count: state.count + 1}))

  render() {
    return (
        <div> <p>You clicked {this.state.count} times</p> <button onClick={this.addCount}> Click me </button> </div>
    );
  }
}

const App = props => {
  const stateReducer = (state, change) => {
    if (state.count >= 4) {
      return {}
    }
    return change
  }
  return <Counter stateReducer={stateReducer}/> } 複製代碼

Controlled Components

Controlled Components將原來子組件改變state的邏輯移到父組件中,由父組件控制。一個運用Controlled Components最廣泛的例子就是<input/>,不傳任何props的狀況下React能夠直接用默認的<input/>組件,可是若是加了value 屬性,若是不傳onChange屬性開始輸入的話, input框不會有任何變化, 由於<input/>已經被父組件控制, 須要父組件指定一個方法, 告訴子組件value如何變化.

class App extends Component{
  state = {
    value: '',
  }

  onChange = (e) => {
    this.setState({value: e.target.value})
  }

  render() {
    return (
          <input value={this.state.value} onChange={this.onChange}/> ); } } 複製代碼

下面是一個實際的例子, 若是給Counter組件傳入count屬性的話, Counter組件本身的addCount就再也不起做用, 須要用戶傳入一個新的addCount方法來決定count如何變化.

class Counter extends Component{
  state = {
    count: 0
  }

  isControlled(prop) {
    return this.props[prop] !== undefined
  }

  getState() {
    return {
      count: this.isControlled('count') ? this.props.count : this.state.count,
    }
  }

  addCount = () => {
    if (this.isControlled('count')) {
      this.props.addCount()
    } else {
      this.setState(state => ({count: state.count + 1}))
    }
  }

  render() {
    return (
        <div> <p>You clicked {this.getState().count} times</p> <button onClick={() => this.addCount()}> Click me </button> </div>
    );
  }
}

class App extends Component{
  state = {
    count: 0,
  }

  addCount = () => {
    this.setState(state => ({count: state.count + 2}))
  }

  render() {
    return (
        <Fragment> {/*this counter add 1 every time*/} <Counter/> {/*this counter add 2 every time*/} <Counter count={this.state.count} addCount={this.addCount}/> </Fragment> ); } } 複製代碼

Hook

Hook 是一些可讓你在函數組件裏「鉤入」 React state 及生命週期等特性的函數,用戶能夠在不使用class的狀況下用一些 React 的特性,如state等等.

useState 就是一個 HookuseState 惟一的參數就是初始 state,經過在函數組件裏調用它來給組件添加一些內部 state。React 會在重複渲染時保留這個 state。useState 會返回一對值:當前狀態和一個讓你更新它的函數,你能夠在事件處理函數中或其餘一些地方調用這個函數。它相似 class 組件的 this.setState,可是它不會把新的 state 和舊的 state 進行合併。

你以前可能已經在 React 組件中執行過數據獲取、訂閱或者手動修改過 DOM。咱們統一把這些操做稱爲「反作用」,或者簡稱爲「做用」。

useEffect 就是一個 Effect Hook,給函數組件增長了操做反作用的能力。它跟 class 組件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具備相同的用途,只不過被合併成了一個 API。

useContext則能夠傳入Context對象,獲取當前的context value. 當相應的Context.Provider更新value, useContext會觸發rerender, 並傳入最新的context value

import React, { useState, useEffect, useContext } from 'react';

const TitleContext = React.createContext({})
const TitleProvider = TitleContext.Provider

function Counter() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Update the document title using the browser API
    console.log(`You clicked ${count} times`)
  });

  const contextTitle = useContext(TitleContext)
  return (
      <div> <h1>{contextTitle}</h1> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
  );
}

const App = props => (
    <TitleProvider value={'Counter'}> <Counter/> </TitleProvider>
)
複製代碼

如下是React全部的hook:

參考:

medium.com/@soorajchan…

blog.logrocket.com/guide-to-re…

engineering.dollarshaveclub.com/reacts-stat…

zh-hans.reactjs.org/docs/hooks-…

medium.com/yazanaabed/…

相關文章
相關標籤/搜索