5. React Context

2019-01-29  本文已影响0人  Jason_Shu

接上一节:https://www.jianshu.com/p/4c58202bcc45 的遗留问题:如果有好几层的组件,那我们传数据是不是要一层层的传过去?

我们来举例说明:

原生JS:

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)
    f4(n3)
}

function f4(n4) {
    console.log(4, n4)
}

{
    let n = 100;
    f1(n);
}

如上述代码,如果f1,f2,f3,f4都要获取n的值并输出,就必须都传到函数中作为参数。

接下来我们用React来模拟上面的需求。

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function F1(props) {
  return(
    <div>
      1, {props.n1}
      <F2 n2={props.n1}/>
    </div>
  );
}

function F2(props) {
  return (
    <div>
      2, {props.n2}
      <F3 n3={props.n2}/>
    </div>
  );
}

function F3(props) {
  return (
    <div>
      3, {props.n3}
      <F4 n4={props.n3}/>
    </div>
  );
}


function F4(props) {
  return (
    <div>
      4, {props.n4}
    </div>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      n: 99
    };
  }
  render() {
    return (
      <div className="App">
        <F1 n1={this.state.n}/>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

每次就像"传家宝"一样传递到最里层。那我们能不能直接传到F4函数里面呢?我们需要建立一个「局部全局变量」。

我们拿原生JS举例:

{
    let x = {};

    // 暴露一个方法使得下面的局部作用于可以修改本作用域的x变量,其他作用域不能直接修改x
    window.setX = function(key, value) {
        x[key] = value;
    }

    // 将f1函数暴露出来,供下面的局部作用域使用
    window.f1 =  function f1() {
        console.log(1)
        f2();
    }

    function f2() {
        console.log(2)
        f3()
    }

    function f3() {
        console.log(3)
        f4()
    }

    function f4() {
        console.log(4, x['n'])
    }

}


{
    window.setX("n", 100);
    setTimeout(() => {
        window.f1()
    }, 1000)
}

上述代码第一个作用域中的「x」就是「context」。

我们接着换用React,在React中使用「Context」。

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const nContext = React.createContext();

function F1() {
  return (
    <div className="bordered">
      1,
      <F2 />
    </div>
  );
}

function F2() {
  return (
    <div className="bordered">
      2,
      <F3 />
    </div>
  );
}

function F3() {
  return (
    <div className="bordered">
      3,
      <nContext.Consumer>
        {n => <F4 n4={n} />}
      </nContext.Consumer>
    </div>
  );
}

function F4(props) {
  return (
    <div className="bordered">
      4, {props.n4}
    </div>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      n: 99
    };
  }
  render() {
    return (
      <div className="App">
        <nContext.Provider value={99}>
          <F1 n1={this.state.n} />
        </nContext.Provider>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

上述代码中,我们用「nContext.Provider」包裹住「App」里面的组件(此例是F1),这样在F1里面的子组件就可以通过「nContext.Consumer」来直接传递所需数据(此处是n),比如F4需要使用n的值,就可以在使用「F4」的时候,用「nContext.Consumer」包裹住它。

我们来解释下为啥可以用「nContext.Consumer」包裹住「F4」来达到效果。

<nContext.Consumer>
     {n => <F4 n4={n} />}
</nContext.Consumer>
function Consumer(props) {
  let x = 100;
  console.log(props.children)
  return <div />;
}

function F4() {
  return <div>0</div>;
}

function App() {
  return (
    <div className="App">
      <Consumer>
        {F4}
      </Consumer>
    </div>
  );
}
<Consumer>
    {F4}
</Consumer>

上述代码中,{F4}在<Consumer>, 其实返回的是「F4」的函数,我们可以在Consumer函数中调用console.log(props.children)看到。既然在Consumer函数中可以得到F4这个函数,那么这个函数就可以调用了。

function Consumer(props) {
  let x = 100;
  let result = props.children(x);
  return (<div>
    {result}
  </div>);
}

function F4(n) {
  return <div>{n}</div>;
}

function App() {
  return (
    <div className="App">
      <Consumer>
        {F4}
      </Consumer>
    </div>
  );
}

上述代码中,在Consumer函数里,我们执行props.children(x),返回的是F4函数的返回值。

那如果我们想更改Context里面的值呢?

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      n: 99
    };
    setTimeout(() => {
      this.setState({
        n: 50
      });
    }, 3000);
  }
  render() {
    return (
      <div className="App">
        <nContext.Provider value={this.state.n}>
          <F1 n1={this.state.n} />
        </nContext.Provider>
      </div>
    );
  }
}

上述代码中,我们直接在3秒后,改变「this.state.n」的值,因为nContext.Provider 中的value值是「this.state.n」,所以里面需要改变的都渲染了。

那我们能不能让F4自己来改变这个值呢?我们可以把值和方法都放到this.state里面,然后传给「nContext.Provider」的value。

const nContext = React.createContext();

function F1() {
  return (
    <div className="bordered">
      1,
      <F2 />
    </div>
  );
}

function F2() {
  return (
    <div className="bordered">
      2,
      <F3 />
    </div>
  );
}

function F3() {
  return (
    <div className="bordered">
      3,
      <nContext.Consumer>
        {x => <F4 n4={x.n} setN={x.setN} />}
      </nContext.Consumer>
    </div>
  );
}

function F4(props) {
  return (
    <div className="bordered">
      4, {props.n4}
      <button onClick={props.setN}>click</button>
    </div>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      x: {
        n: 50,
        setN: () => {
          this.setState({
            x: {
              ...this.state.x,
              n: this.state.x.n + 1
            }
          });
        }
      }
    };
  
  }
  render() {
    return (
      <div className="App">
        <nContext.Provider value={this.state.x}>
          <F1 />
        </nContext.Provider>
      </div>
    );
  }
}

其实本质上还是像之前一样,App组件里面的n,要想改变它,也要在App组件里面写方法(this.setState()),然后一层层通过props传到里层组件,比如「App」->「F1」->「F2」->「F3」->「F4」,现在有了「Context」相当于直接传到「F4」,即「App」->「F4」。

image.png
上一篇下一篇

猜你喜欢

热点阅读