你想知道的 React 组件设计模式这里都有(下)

上一篇介绍了三种设计模式,包括1.容器与展现组件 2.高阶组件 3.render props。html

这篇咱们继续介绍三种设计模式,包括1.context模式 2.高阶组件 3.继承模式java

为了更好的理解,你能够将相应源码下载下来查看:源码地址react

Context模式

概念介绍

React 的 Context 接口提供了一个无需为每层组件手动添加 props ,就能在组件树间进行数据传递的方法。git

在一个典型的 React 应用中,数据是经过 props 属性自上而下(由父及子)进行传递的,但这种作法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都须要的。Context 提供了一种在组件之间共享此类值的方式,而没必要显式地经过组件树的逐层传递 props。github

示例

React v16.3.0先后的Context相关API不一样,这边只介绍新版本的Context使用方法。设计模式

第一步:新建createContext

首先,要用新提供的 createContext 函数创造一个“上下文”对象。bash

const ThemeContext = React.createContext();
复制代码

第二步:生成Provider 和 Consumer

接着,咱们用ThemeContext生成两个属性,分别是Provider和Consumer。从字面意思便可理解。Provider供数据提供者使用,Consumer供数据消费者使用。antd

const ThemeProvider = ThemeContext.Provider;
const ThemeConsumer = ThemeContext.Consumer;
复制代码

第三步:使用ThemeProvider给数据提供者

const Context = () => {
  return (
    <div>
      <ThemeProvider value={{ mainColor: 'blue', textColor: 'pink' }} >
        <Page />
      </ThemeProvider>
    </div>
  )
}

// 调用context
const Page = () => (
  <div>
    <Title>标题</Title>
    <Content>
      内容
    </Content>
  </div>
);
复制代码

第四步:使用ThemeConsumer给数据接收者

// 这里演示一个class组件。Counsumer使用了renderProps模式哦。
class Title extends React.Component {
  render() {
    return (
      <ThemeConsumer>
        {
          (theme) => (
            <h1 style={{ color: theme.mainColor }}>
              {this.props.children}
            </h1>
          )
        }
      </ThemeConsumer>
    );
  }
}

// 这里演示一个函数式组件
const Content = (props, context) => {
  return (
    <ThemeConsumer>
      {
        (theme) => (
          <p style={{ color: theme.textColor }}>
            {props.children}
          </p>
        )
      }
    </ThemeConsumer>
  );
};
复制代码

模式所解决的问题

Context 主要应用场景在于不少不一样层级的组件须要访问一样一些的数据。以下图,组件a、组件g、组件f须要共享数据,则只须要在最外层套上Provider,须要共享的组件使用Consumer便可。ide

注意事项

由于 context 会使用参考标识(reference identity)来决定什么时候进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,如下的代码会重渲染全部下面的 consumers 组件,由于 value 属性老是被赋值为新的对象:函数

class App extends React.Component {
  render() {
    return (
      <Provider value={{something: 'something'}}>
        <Toolbar />
      </Provider>
    );
  }
}
复制代码

为了防止这种状况,将 value 状态提高到父节点的 state 里:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

复制代码

Compound Component

概念介绍

Compound Component 翻译为组合组件。借用组合组件,使用者只须要传递子组件,子组件所须要的props在父组件会封装好,引用子组件的时候就不必传递全部props了。

组合组件核心的两个方法是React.Children.mapReact.cloneElement。React.Children.map 用来遍历得到组件的子元素。React.cloneElement 则用来复制元素,这个函数第一个参数就是被复制的元素,第二个参数能够增长新产生元素的 props ,咱们就是利用这个函数,把想要的 props 传入子元素。

示例

咱们设计一个相似于 antd 中的 Tabs 组件,提供tab切换功能,而被选中的TabItem须要高亮。

若是咱们使用常规写法,用 Tabs 中一个 state 记录当前被选中的 Tabitem 序号,而后根据这个 state 传递 props 给 TabItem,还须要传递一个 onClick 事件进去,捕获点击选择事件。

<TabItem active={true} onClick={this.onClick}>One</TabItem>
<TabItem active={false} onClick={this.onClick}>Two</TabItem>
<TabItem active={false} onClick={this.onClick}>Three</TabItem> 
复制代码

每次增长一个TabItem,是否是都须要传递active和onClick,这太繁琐了!咱们用compound模式解决这个问题。

const TabItem = (props) => {
  const {active, onClick} = props;
  const tabStyle = {
    'max-width': '150px',
    color: active ? 'red' : 'green',
    border: active ? '1px red solid' : '0px',
  };
  return (
    <h1 style={tabStyle} onClick={onClick}>
      {props.children}
    </h1>
  );
};

// jsx调用Tabs以及TabItem
const Compound = (props, context) => {
  return (
    <Tabs>
      <TabItem>One</TabItem>
      <TabItem>Two</TabItem>
      <TabItem>Three</TabItem>
      <TabItem>Four</TabItem>
    </Tabs>
  );
};
复制代码

上面的代码展现了咱们最终调用Tabs以及TabItem的样子。重点在于Tabs咱们要如何实现:

class Tabs extends React.Component {
  state = {
    activeIndex:  0
  }
  render() {
    const newChildren = React.Children.map(this.props.children, (child, index) => {
      if (child.type) {
        return React.cloneElement(child, {
          active: this.state.activeIndex === index,
          onClick: () => this.setState({activeIndex: index})
        });
      } else {
        return child;
      }
    });
    return (
      <Fragment>
        {newChildren}
      </Fragment>
    );
  }
}
复制代码

本来咱们要如此调用:

<TabItem active={false} onClick={this.onClick}>One</TabItem>
复制代码

如今咱们这样调用就能够了:

<TabItem>One</TabItem>
复制代码

经过组合使用React.Children.mapReact.cloneElement,咱们让TabItem得到了它想要的属性,简化了TabItem的使用,是否是很神奇!

模式所解决的问题

组合组件设计模式通常应用在一些共享组件上。如 select 和 option , Tab 和TabItem 等,经过组合组件,使用者只须要传递子组件,子组件所须要的 props 在父组件会封装好,引用子组件的时候就不必传递全部 props 了。

咱们能够在共享的组件中运用这种模式,简化组件使用者的调用方式,antd 当中你就能看到许多组合组件的使用。

继承模式

说了那么多的模式,咱们最后来谈谈很熟悉的继承模式。若是组件定义为class组件,那么咱们固然可使用继承的模式来实现组件的复用。

示例

咱们经过一个基类来实现一些通用的逻辑,而后再经过继承分别实现两个子类。

class Base extends React.PureComponent {
  getAlbumItem = () => {
    return null
  }
  render () {
    return (
      <div style={{border:'1px solid red',margin:5,width:300}}>
        {this.getAlbumItem()}
        <div>通用逻辑写这里</div>
      </div>
    )
  }
}
class Mobile extends Base {
  getAlbumItem = () => {
    return <span>mobile</span>
  }
}
class Pc extends Base {
  getAlbumItem = () => {
    return <span>pc</span>
  }
}
复制代码

咱们能够看到Mobile和Pc共享了Base的逻辑,实现了复用。

组合与继承

若是你刚使用React,可能继承的方式对你们来讲更熟悉的。由于继承看起来很方便,也很好理解。可是React官方并不推荐使用继承,由于各类组合的模式彻底足够使用,上面的例子咱们彻底能够用组合的思想去实现。

为何不推荐使用继承?继承有两个缺点,其一是,父类的属性和方法,子类是无条件继承的。也就是说,无论子类愿意不肯意,都必须继承父类全部的属性和方法,这样就不够灵活了。其二是,js中class并不直接支持多继承。这两个缺点使得继承相对于组合组件缺乏了灵活性以及可扩展性。

请记住,组合优于继承!组件的复用请第一时间想到使用组合而非继承。

尾声

到这里,六种React组件设计模式就就讲完了。这六种模式已经覆盖了绝大多数的组件使用场景。随着React的更新,也许未来会有更多组件设计模式出现。可是思想都是想通的,好比“责任分离”、“不要重复本身”(DRY,Don't Repeat Yourself) 等等。明白这些代码设计思想,未来咱们也能很快地掌握新的组件设计模式。

参考连接:

相关文章
相关标签/搜索