迁移到React的新Context API(附代码示例)

82 阅读4分钟

随着最近React 16.3.0的发布,官方推出了上下文API。你可以从我之前的博文中了解更多关于这个API背后的原因和方法。"React的⚛️新上下文API"。由于这一重大变化,我正在对我在 egghead.io 上的高级组件模式课程进行更新,以使用新的 API 而不是旧的。在我更新课程的过程中,我一直在从旧的上下文API迁移到新的,我想我应该向你展示其中的一些变化

在我的课程中,我有一个部分展示了如何编写使用上下文API的复合组件(这是我Ryan Florence那里学到的技巧)。

下面是暴露了复合组件API的Toggle组件的使用例子:

function Usage(props) {
  return (
    
      The button is on
      The button is off
      
        
      
    
  )
}

复合组件模式背后的想法是,它允许你拥有相互共享隐含状态的组件。实际上,对于简单的情况,你可以使用React.Children.map 来实现它,但在这种情况下,我们需要在react树的任何地方共享任何状态的上下文。

这里是使用旧的上下文API的实现版本:

const TOGGLE_CONTEXT = '__toggle__'
function ToggleOn({children}, context) {
  const {on} = context[TOGGLE_CONTEXT]
  return on ? children : null
}
ToggleOn.contextTypes = {
  [TOGGLE_CONTEXT]: PropTypes.object.isRequired,
}
function ToggleOff({children}, context) {
  const {on} = context[TOGGLE_CONTEXT]
  return on ? null : children
}
ToggleOff.contextTypes = {
  [TOGGLE_CONTEXT]: PropTypes.object.isRequired,
}
function ToggleButton(props, context) {
  const {on, toggle} = context[TOGGLE_CONTEXT]
  return 
}
ToggleButton.contextTypes = {
  [TOGGLE_CONTEXT]: PropTypes.object.isRequired,
}
class Toggle extends React.Component {
  static On = ToggleOn
  static Off = ToggleOff
  static Button = ToggleButton
  static defaultProps = {onToggle: () => {}}
  static childContextTypes = {
    [TOGGLE_CONTEXT]: PropTypes.object.isRequired,
  }
  state = {on: false}
  toggle = () =>
    this.setState(
      ({on}) => ({on: !on}),
      () => this.props.onToggle(this.state.on),
    )
  getChildContext() {
    return {
      [TOGGLE_CONTEXT]: {
        on: this.state.on,
        toggle: this.toggle,
      },
    }
  }
  render() {
    return {this.props.children}
  }
}

在旧的API中,你必须在getChildContextchildContextTypes 中为你的组件提供什么样的上下文指定一个字符串,然后在消费组件中用contextTypes 指定相同的字符串。我从来不喜欢这种间接性,通常通过像我上面那样建立一个变量来避免这个问题。此外,必须给消费者附加静态属性,以便他们能够接受上下文的值,这也不是我喜欢做的事情。

这个API的另一个问题是,它不允许通过返回shouldComponentUpdate ,更新数值false 。所以我有一整节课来演示如何解决这个问题。"Rerender Descendants Through shouldComponentUpdate"(向Michael JacksonRyan Florencereact-broadcast致敬)。

新的API没有这些问题,这是我对它如此兴奋的部分原因。这是我对同一练习的新版本:

const ToggleContext = React.createContext({
  on: false,
  toggle: () => {},
})

class Toggle extends React.Component {
  static On = ({children}) => (
    
      {({on}) => (on ? children : null)}
    
  )
  static Off = ({children}) => (
    
      {({on}) => (on ? null : children)}
    
  )
  static Button = props => (
    
      {({on, toggle}) => }
    
  )
  toggle = () =>
    this.setState(
      ({on}) => ({on: !on}),
      () => this.props.onToggle(this.state.on),
    )
  state = {on: false, toggle: this.toggle}
  render() {
    return (
      
        {this.props.children}
      
    )
  }
}

在这里的变化中,有几件事让我印象深刻。正如我所说,旧的API的问题已经消失了。现在,你有了明确的组件,而不是字符串的间接性,你必须使用这些组件来提供和消费上下文。你不再需要奇怪的属性来使事情运转,而是使用简单的组件。

不过,这些复合组件还是有点啰嗦。他们中的每一个人都需要使用消费者(就像他们中的每一个人都需要静态属性)。你可以用一个基于渲染参数的高阶组件来解决这个问题(对于这两个API)。在这种情况下,我不会去管它,这很简单。

另一个消失的问题是通过shouldComponentUpdate 返回false 的更新。React的新上下文API为你解决了这个问题。

我喜欢这一点的另一个原因是,由于消费者使用的是渲染道具API,它们具有高度的可组合性,因此可以用它们做任何事情,并在上面暴露出一个漂亮干净的API,因为渲染道具的动态可组合性(相对于旧API的静态可组合性)。

有一个非常常见的陷阱,我相信我们会永远和它作斗争,那就是你给Provider 组件的value 道具只有在你想让消费者重新渲染时才会改变,这一点非常重要。**这意味着在我们的render 方法中做value={{on: this.state.on, toggle: this.toggle}} 是不可取的,因为每次调用render 都会创建一个新的对象,即使状态实际上没有改变。**因为它是一个新的对象,所有的消费者也将被重新渲染。

这样做的影响在实践中会有很大的不同,但一般来说,最好是提供一个只有在状态改变时才会改变的值(消费者需要被重新渲染)。这就是为什么我说value={this.state} 。如果你不希望把整个状态对象暴露给消费者,那么你可以使用我从Ryan Florence那里得到的这个技巧

我对此有一个小问题,就是我必须把toggle 方法放到state ,这让我感觉很奇怪,但这是一个实现细节,我认为不是什么大事。

在将一些使用上下文的组件转换到新的API之后,我确信React团队给了我们一些出色的东西。我喜欢这个新的API,我渴望看到社区如何接受它!我希望你喜欢它。我希望你喜欢它。祝你们好运!