React: Context API 入门

作者: 写代码的海怪 | 来源:发表于2018-12-20 16:33 被阅读41次

    说到使用全局数据管理状态时,第一个想到的就是 Redux。但是 Redux 语法太过啰唆,React 使用者一直很不爽。所以 React 模仿 Redux 推出了 Context API。

    一个例子

    我们依旧从一个例子出发,现在有 3 个函数,规定:

    1. f1 调用 f2
    2. f2 调用 f3
    3. 有局部变量 n,问:怎么在 f3 中拿到这个n
      我们可能会这样想,每次我都传这个n到下一个组件不就好了?比如:
    function f1(n1) {
        console.log(1, n1);
        f2(n1);
    }
    
    function f2(n2) {
        console.log(2, n2);
        f3(n2);
    }
    
    function f3(n3) {
        console.log(3, n3);
    }
    
    {
        let n = 100;
        f1(n);
    }
    

    是很简单,但是很不爽。

    局部的全局变量

    如果我们将这个n提到全局,那么所有的函数都能访问到这个n就能解决第三个需求了。但是这违反了n的作用域要求。毕竟全局变量要慎用的。

    但是我们可以将两者做一个结合。先给这些函数一个局部作用域,在里面定义一个store来存放n,如果要改变n,那么在里面提供一个接口,让外面去改这个n就好了。比如:

    {
        let context = {};
        window.setContext = function (key, value) {
            context[key] = value;
        };
        window.f1 = function f1(n1) {
            f2();
        };
    
        function f2(n2) {
            f3();
        }
    
        function f3(n3) {
            console.log(4, context["n"]);
        }
    }
    
    {
        window.setContext("n", 100);
        f1();
    }
    

    这个其实就是 Context API 的大致思想:我画一个圈,在这个圈子里变量可以被里面所有人访问,但是圈子之外的人不能访问。

    回到组件问题

    现在回到我们的组件问题,深层组件怎么很容易地去获取全局变量?考虑如下代码,要怎么在MyBox组件里获取 App 里的 theme呢?

    function MyDiv(props) {
        return <div className={怎么在这里获取 theme 呢?}>{props.children}</div>
    }
    
    function MyButton() {
        return <button>Click</button>
    }
    
    function MyInput() {
        return <input type="text"/>
    }
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                theme: 'light'
            }
        }
        render() {
            return (
                <div>
                    <button onClick={this.change}></button>
                    <MyDiv>
                        <MyButton></MyButton>
                    </MyDiv>
                    <MyDiv>
                        <MyInput></MyInput>
                    </MyDiv>
                </div>
            )
        }
    }
    

    Context API 就能完美地解决,而且不能每都传这个 theme

    使用 Context API

    造泡泡

    使用下面代码创建 Context,相当于先造一个泡泡(前面的创建一个局部环境)给这些要用到全局变量的组件。

    const themeContext = React.createContext()
    

    给泡泡上色

    虽然在组件 Appstate 里定义了 theme,但是我们还要添加 Provider 的方式将这个 theme 让上面创建的局部环境能获取这个 theme

    <themeContext.Provider value={this.state.theme}>
        <div>
            <button onClick={this.change}>Change theme</button>
            <MyDiv>
                <MyButton></MyButton>
            </MyDiv>
            <MyDiv>
                <MyInput></MyInput>
            </MyDiv>
        </div>
    </themeContext.Provider>
    

    包住组件

    现在只需要将组件包在上面创建的局部环境里就可以访问到了。

    <themeContext.Provider value={this.state.theme}>
        <button onClick={this.change}>Change theme</button>
        <themeContext.Consumer> 
            {theme => (
            <div>
                <MyDiv theme={theme}>
                    <MyButton></MyButton>
                </MyDiv>
                <MyDiv theme={theme}>
                    <MyInput></MyInput>
                </MyDiv>
            </div>)}
        </themeContext.Consumer>
    </themeContext.Provider>
    

    在组件 MyDiv 里可以用 props 获取。

    function MyDiv(props) {
        return <div className={props.theme}>{props.children}</div>
    }
    

    看到这你会不会蒙逼?前面都好理解,这个Consumer里的是什么鬼,又箭头,又函数,又标签,不懂啊。

    聪明的 React

    字符串?函数?标签?

    我们先从一个简单的例子开始。

    function Component() {
        console.log('Hello world')
        return <p>hi</p>
    }
    
    function Consumer(props) {
        return <div>{props.children}</div>
    }
    
    function App() {
        return (
            <Consumer>
                Component
                {Component}
                <Component/>
            </Consumer>
        )
    }
    

    这里猜猜会变成啥。记住,我们写 React 的时候,JSX 里的标签不是 HTML 标签,而是 JS 代码。

    • Component 会变成字符串,就显示 "Component" 在页面上
    • {Component} 会变成函数,由于 React 的限制,不会显示
    • <Component/> 则会翻译成 React.createElement('p', null) 也就是显示成 <p>hi</p>

    我们注意到第二种情况 {Component} 虽然不显示,但是我们可以在 App 通过 this.props.children 去获取 Component 这个函数。

    标签里传数函数

    再考虑如下例子。

    function Component(msg) {
        console.log(msg)
        return <p>hi</p>
    }
    
    function Consumer(props) {
        let msg = 'Hello world'
        let fakeHtml = props.children(msg)
        return fakeHtml
    }
    
    function App() {
        return ( <Consumer>{Component}</Consumer>)
    }
    
    1. 先看组件 App 里面 组件 Consumer 里有一个 {Component}
    2. 再看组件 Consumer 里,我们可以用 this.props.children 去获取 Component 这个函数
    3. 然后就执行这个函数,同时传入 msg ,在 Component 函数里打印出来,并返回 Component 里返回的“标签”

    看到这里有没有启发呢?其实 themeContext.Consumer 做的就是这样的事,也就是:

    1. themeContext.Consumer 里先用 props.children 去获取标签里的箭头函数
    2. 然后将 theme 作为这个箭头函数的第一参数传进去
    3. 箭头函数返回的结果的 JSX “标签”(注意:这不是标签是JS代码)就用上了箭头函数传入的 theme

    改值

    上面很容易获取值,那么修改值呢?

    App 上改值

    上面的例子就在组件 App 里直接改值,加个函数 change 就好了,然后在对应的按钮绑定:

    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                theme: 'light'
            }
        }
        change = () => {
            this.setState({
                theme: 'dark'
            })
        }
        render() {
            return (
                <themeContext.Provider value={this.state.theme}>
                    <button onClick={this.change}>Change theme</button>
                    <themeContext.Consumer> 
                        {theme => (
                        <div>
                            <MyDiv theme={theme}>
                                <MyButton></MyButton>
                            </MyDiv>
                            <MyDiv theme={theme}>
                                <MyInput></MyInput>
                            </MyDiv>
                        </div>)}
                    </themeContext.Consumer>
                </themeContext.Provider>
            )
        }
    }
    

    在别的组件里改值

    这个也很容易,我们可以不传 theme,可以在 value 里传入一个对象如:

    class App extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                store: {
                    theme: 'light',
                    setTheme: () => {
                        this.setState({
                            store: {
                                ...this.state.store,
                                theme: 'dark'
                            }
                        })
                    }
                }
            }
        }
        render() {
            return (
                <themeContext.Provider value={this.state.theme}>
                    <button>Change theme</button>
                    <themeContext.Consumer> 
                        {store => (
                        <div>
                            <MyDiv theme={store.theme}>
                                <MyButton onClick={store.setTheme}></MyButton>
                            </MyDiv>
                            <MyDiv theme={store.theme}>
                                <MyInput></MyInput>
                            </MyDiv>
                        </div>)}
                    </themeContext.Consumer>
                </themeContext.Provider>
            )
        }
    }
    

    总结

    1. 使用 React.createContext() 来创建 Context
    2. 使用 <XXXContext.Provider value={obj}>...</XXXContext.Provider>传值
    3. 使用<XXXContext.Consumer>{(obj) => <F1 myValue={obj.xx}/>}</XXXContext.Consumer>包住组件
    4. obj 里可以有 store, setStore,能读又能取
    5. 思想:使用局部的全局变量,只能在<XXXContext.Consumer/>里使用。要用变量的时候就加一个局部环境给那些要用的组件,这样也不会影响别的组件。

    有没有感觉这个比 Redux 舒服很多呢?没有一大堆的概念和摸不着头脑的代码分离,只有简单的传值。
    (完)

    相关文章

      网友评论

        本文标题:React: Context API 入门

        本文链接:https://www.haomeiwen.com/subject/uvvykqtx.html