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/…

相关文章
相关标签/搜索