5. React Context
接上一节: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