前言
本文将通过官方实例,组合运用Context和高阶组件,并给出实例代码的方式详细介绍Context、高阶组件的概念和用法。
Context
在典型的React应用中,数据是通过props属性一层层的由上向下传递。但是对于应用中很多组件都需要使用的值(比如说:页面的主题,页面语言选择),如果还是通过props属性一层层的传递下来,会非常繁琐。
Context通过组件树提供了一种传递数据的方法,可以避免在每一层手动的传递props属性,却能让组件共享此类数据。
设计目的
Context的设计目的是为了共享那些被组件树认为是“全局”的数据。
注意:
不要仅仅为了避免使用props在几个层级下的组件传递数据,而使用Context,它被用于多个层级的多个组件需要使用相同数据的场景。
高阶组件
高阶组件是一个没有副作用的纯函数,该函数接受一个组件作为参数,并返回一个新的组件。高阶组件并不是一个React API,而是一种模式。
组件是将props属性转成UI,而高阶组件是将组件转成一个新的组件。高阶组件通过将原组件包裹在容器组件里面的方式来组合使用原组件。
如果大家使用Redux作为状态管理容器,那么大家对React-Redux中的connect很熟悉,面试的时候,我老是把connect说成高阶组件,但是它的形式却并不附和我们上面介绍的高阶组件。其实connect是一个返回接受单参数的高阶组件的高阶函数。connect返回的高阶组件的形式是下面这样的:
component => component
上述形式的高阶组件有个显著优点:输入和输出类型相同的函数是很容易组合在一起的。
下面我们将通过例子详细介绍高阶组件到底可以做什么。
实例
我们以页面主题为例,使用Context实现数据的传递。并实现动态Context的功能,通过添加一个功能按钮,实现Context传递的数据的切换。
第一步:我们首先建立一个Context.js:
//Context.js
export const themes = {
light: {
foreground: '#ffffff',
background: '#222222',
},
dark: {
foreground: '#000000',
background: '#eeeeee',
},
};
export const ThemeContext = React.createContext(
themes.dark // default value
);
上述代码使用了React.createContext,用法如下:
const {Provider, Consumer} = React.createContext(defaultValue);
当 React 渲染 context 组件 Consumer 时,它将从组件树的上层中最接近的匹配的 Provider 读取当前的 context 值。
如果上层没有匹配的Provider,而此时我们渲染了一个Consumer组件,那么此时就会用到我们创建Context的时候用到的defaultValue。
第二步,我们原本会打算创建需要使用页面主题数据的组件,但是我们想一下:我们的React应用不可能只有一个组件会用到我们定义的主题Context,如果给每个组件都一个个的手动引用主题Context,会很麻烦。这个时候我们就可以使用高阶组件了。所以我们创建下面这个高阶组件:
//WithTheme.js
import { ThemeContext } from "./context"
export function WithTheme(Component) {
return class extends Component{
render(){
const { props } = this;
return(
<ThemeContext.Consumer>
{theme => <Component {...props} theme = {theme}/>}
</ThemeContext.Consumer>
)
}
}
}
从上面可以看出来,想要使用定义的Context,只需要在原组件包裹在React.createContext生成的Consumer里面就可以了。
接下来,我们创建一个需要使用页面主题数据的组件ThemeButton.js:
//ThemeButton.js
class ThemeButton extends Component{
constructor(props){
super(props);
this.title = "button component"
}
render(){
const {props} = this;
return(
<div>
<button style={{color:props.theme.foreground,background:props.theme.background}}>{props.title}</button>
</div>
)
}
}
//WithTheme就是上面定义的高阶组件
export default WithTheme(ThemeButton);
接下来我们创建一个展示组件,里面包含上述ThemeButton组件和一个用于切换主题的按钮。代码如下:
//Toolbar.js
import ThemeButton from "./ThemeButton"
class Toolbar extends Component{
constructor(props){
super(props);
}
render(){
const { props } = this;
return(
<div>
<ThemeButton title="按我吧" />
//这里的changeTheme处理函数从父组件传入
<button onClick = { props.changeTheme }>
别按我
</button>
</div>
)
}
}
export default Toolbar;
最后我们创建一个容器组件,用于管理所有的数据和处理程序:
//Container.js
import { ThemeContext,themes } from "./context"
import Toolbar from "./Toolbar"
class Container extends Component{
constructor(props){
super(props);
this.state ={
theme:themes.light
};
this.toggleTheme = this.toggleTheme.bind(this);
}
toggleTheme(){
this.setState(state =>({
theme:state.theme === themes.dark ? themes.light:themes.dark
}))
}
render(){
return(
<div>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
</div>
)
}
}
export default Container;
从上面可以看出来,想要为使用ThemeContext的组件提供值,只需要使用React.createContext生成的Provider包裹展示组件就可以了。