你不知道的JavaScriptReact.js饥人谷技术博客

React: Context API 入门

2018-12-20  本文已影响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} 虽然不显示,但是我们可以在 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 舒服很多呢?没有一大堆的概念和摸不着头脑的代码分离,只有简单的传值。
(完)

上一篇下一篇

猜你喜欢

热点阅读