Provider模式
许多的React库都需要在所有的组件树中传递数据。比如说,Redux需要传递他的store,而React Router需要传递当前的地址。
一个看似可行的方案时使用共享的可变状态,但这只能在客户端使用。当我们需要使用服务器端预渲染时,这种方案不可靠。
所幸,React提供了一种自上而下传递数据的途径: context。我们可以把它看做组件树的一个全局变量。
在app的最外部,我们可以提供一个Provider,它的唯一角色就是给当前的组件树的context增加数据,来提供给所有的子节点使用。
我们将使用”主题”的例子来解释这个模式: 我们需要将自定义的主题信息传递到app的每个地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { Component, PropTypes, Children } from "react" class ThemeProvider extends Component { static propTypes = { theme: PropTypes.object.isRequired, } static childContextTypes = { theme: PropTypes.object.isRequired, } getChildContext() { const { theme } = this.props return { theme } } render() { return Children.only(this.props.children) } } export default ThemeProvider
|
有了这个Provider,我们可以将theme
传递给任何需要它的组件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from "react" import { render } from "react-dom" import ThemeProvider from "ThemeProvider" import App from "App" const mountNode = document.querySelector("#App") const theme = { color: "#cc3300", fontFamily: "Georgia", } render( <ThemeProvider theme={theme}> <App /> </ThemeProvider>, mountNode )
|
好了,现在theme
被添加到了context
中,我们还需要给组件一个简单的方法能够取到这个数据,这里我们将使用第二个模式。
Higher-Order Component 模式(HOC)
可以说,每个需要使用theme
的组件都要声明一个静态的contextTypes
属性。
但这其实是一个不明智的做法,原因有下面两点:
还有一个潜在的解决方案是通过继承,但这也不是一个完美的方案,理由如下:
多余一层的继承是不明智的: 多余一层的继承通常会导致方法冲突,当修改时,需要我们检查每个父类。
互通性: 在React中,声明组件有三种方式,class extends React.Component {}
, React.createClass({})
和无状态的函数 ({ props }) => …
对于后两种方式来说,都不是继承自React.Component
的。
因此,最好的方法是通过一个可复用的函数创建一个Higher-Order Component(HOC)。基本上来说,我们通过一个仅仅负责获取context
的组件来包裹其他组件,同时将context
作为props
传递。
事实上,我们可以把HOC看做一个app的注入点,将原来的context
注入到props
,这么做有很多的好处:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const React, { Component, PropTypes } from "react" const theme = (ComponentToWrap) => { return class ThemeComponent extends Component { static contextTypes = { theme: PropTypes.object.isRequired, } render() { const { theme } = this.context return ( <ComponentToWrap {…this.props} theme={theme} /> ) } } } export default theme
|
接下来,既可以使用theme
方法来封装任何类型的组件了:
无状态函数
1 2 3 4 5 6 7 8 9 10
| import React from "React" import theme from "theme" const MyStatelessComponent = ({ text, theme }) => ( <div style={{ color: theme.color }}> {text} </div> )
export default theme(MyStatelessComponent)
|
类定义的组件
使用ES7中的decorator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React, { Component } from "React" import theme from "theme" @theme class MyComponent extends Component { render() { const { text, theme } = this.props return ( <div style={{ color: theme.color }}> {text} </div> ) } }
export default MyStatelessComponent
|
直接调用theme
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React, { Component } from "React" import theme from "theme" class MyComponent extends Component { render() { const { text, theme } = this.props return ( <div style={{ color: theme.color }}> {text} </div> ) } }
export default theme(MyStatelessComponent)
|
注意theme
仅仅是一个函数,我们可以使用一个简单的闭包来提供一些选项:
1 2 3 4 5 6 7 8 9 10 11 12
| const theme = (mergeProps = defaultMergeProps) => (ComponentToWrap) => { render() { const { theme } = this.context const props = mergeProps(this.props, { theme }) return ( <ComponentToWrap {…props} /> ) } }
|
使用:
1 2 3 4 5
| const mergeProps = ((ownProps, themeProps) => ({…themeProps, …ownProps}) export default theme(mergeProps)(MyComponent)
|