使用context之前的考虑
Context
Context主要应用场景在于很多不同层级的组件需要访问同样一些的数据。 如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比context更好的方案。
function Parent(){
return <TestChilren name={'子组件name'}>
<TestGrandson name={'孙组件name'}></TestGrandson>
</TestChilren>
}
function TestChilren(props){
return <div> <p>子组件:{props.name}</p> {props.children}</div>
}
function TestGrandson(props){
return <div> <p>孙组件:{props.name}</p></div>
}API介绍和测试
React.createContext
const Mycontent = React.createContext(defaultValue)创建一个Context对象。当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的provider中读取到当前的context值(使用的时候需要使用函数接受参数并返回结果)。 只有当组件所处的树中没有匹配到Provider时候,其defaultValue参数才会生效。这有助于在不适用Provider包装组件的情况下对组件进行测试。注意:将undefined传递给Provider的value时,消费组件的defaultValue不会生效。
context.Provider
<Mycontent.Provider value = {{/* 某个值 */}}>每个Context对象都会返回一个Provider React组件,它允许消费者组件订阅context的变化。 Provider接受一个value属性,传递给消费组件。一个Provider可以和多个消费组件有对应关系。多个provider也可以嵌套使用,里层的会覆盖外层数据。 当Provider的value值发生变化的时,它内部的所有消费组件都会重新渲染。Provider及其内部consumer组件都不受制于shouldComponentUpdate函数,因此但是consumer组件在其祖先组件退出更新的情况下也能更新。
Context.Consumer
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>通过Consumer订阅context变更。
- 没有provider,显示defaultValue
<ThemeContext.Consumer> {value => <div>{value}</div>} </ThemeContext.Consumer> - 有provider,显示provider提供的value,并且可以多层嵌套,始终取最近的provider提供的value.
<ThemeContext.Provider value={props.value}> <ThemeContext.Consumer> {value => <div>{value}</div>} </ThemeContext.Consumer> </ThemeContext.Provider>
使用 context, 我们可以避免通过中间元素传递 props: ``` function TestProvider (props) { return ( <ThemeContext.Provider value={props.value}> </ThemeContext.Provider> ) } function TestConsumer (props) {
return (
<ThemeContext.Consumer>
{value => <div>{value}</div>}
</ThemeContext.Consumer>
)
}
```
Class.contextType
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;or
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* 基于这个值进行渲染工作 */
}
}给类组件加一个静态属性contextType指定某个context,就可以直接在生命周期和render中直接使用this.context访问到它的值。
Context.displayName
context 对象接受一个名为 displayName 的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。
动态Context
对于上面的theme例子,使用动态值(dynamic values)后更复杂的用法:
const themes = {
light:{
color:'#000000',
backgroundColor:'#eeeeee'
},
dark:{
color:'#ffffff',
backgroundColor:'#222222'
}
}
const ThemesContext = React.createContext(
themes.dark
)
class ThemeButton extends React.Component{
static contextType = ThemesContext
render(){
let props = this.props;
let theme = this.context;
return (
<button {...props} style={theme}></button>
)
}
}
function Toolbar (props){
return (
<ThemeButton onClick={props.changeTheme}>
changeTheme
</ThemeButton>
)
}
class App extends React.Component{
constructor(props){
super(props)
this.state={
theme:themes.light
}
this.toggleTheme = () => {
this.setState(state =>({
theme:state.theme === themes.dark ? themes.light : themes.dark
}))
}
}
render () {
// 在 ThemeProvider 内部的 ThemedButton 按钮组件使用 state 中的 theme 值,
// 而外部的组件使用默认的 theme 值
return (
<div>
<ThemesContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemesContext.Provider>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('#root'))在嵌套组件中更新context
从一个在组件树中嵌套很深的组件中更新context是很有必要的。在这种场景下,你可以通过context传递一个函数,使得consumers组件更新context;
const themes = {
light: {
color: '#000000',
backgroundColor: '#eeeeee'
},
dark: {
color: '#ffffff',
backgroundColor: '#222222'
}
}
const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => { }
})
function ThemeTogglerButton () {
return (
<ThemeContext.Consumer>
{
({ theme, toggleTheme }) => (<button onClick={toggleTheme} style={theme}>
Toggle Theme
</button>)
}
</ThemeContext.Consumer>
)
}
class App extends React.Component {
constructor(props) {
super(props)
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
}
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render () {
// 整个 state 都被传递进 provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#root'))消费多个context
为了确保context快速进行重渲染,React需要使每一个consumers组件的context在组件树中成为一个单独的节点。
var ThemeContext = React.createContext('light');
var UserContext = React.createContext({
name:"Guest"
})
class App extends React.Component{
render(){
const {signedInUserm , theme} = this.props;
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
)
}
}
function Layout(){
return (
<div>
{/* <Sidebar /> */}
<Content />
</div>
)
}
// 一个组件可能会消费多个context
function Content(){
return (
<ThemeContext.Consumer>
{
theme => (
<UserContext.Consumer>
{
user => (
<div>
{JSON.stringify(user)} => {theme}
</div>
)
}
</UserContext.Consumer>
)
}
</ThemeContext.Consumer>
)
}
ReactDOM.render(<App theme = {[1,2]} />, document.querySelector('#root'))如果有两根或者更多的context值经常被一起使用,那你可能要考虑一下另外创建你自己的渲染组件,以提供这些值。
注意事项
因为context会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当provider的父组件进行重渲染时候,可能会在consumers组件中触发意外的渲染。例如,当每一次provider重渲染时,以下的代码会重渲染所有下面的consumers组件,因为value属性总是被赋予新的对象。
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.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>
);
}
}