Skip to content

使用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>
    );
  }
}