Context 的各种情境
实际的情境可能更加复杂,如果我们希望切换样式的按钮,是底下深层的子元件呢?我可以一次 pass 好几个 context 下去吗?React 文件提供了更多情境的范例,给大家参考。
情境一:如何我希望切换状态的元件,不在父层?
该如何把切换状态的元件往下传,从子层做到父层状态的控制?除了把控制状态的函式当作 props 一层一层传递,这里也有一个不用层层传递的方法:将 toggle method 包在 context 内。
// 建立 context 的时候,先把 toggle method 定义进去
export const ThemeContext = React.createContext({
theme: themes.night,
toggleTheme: () => {},
});
// 将切换的功能与 state 在父层设定好,跟着 theme 一起包着传下去
...
this.state = {
theme: themes.morning,
toggleTheme: this.toggleTheme,
};
....
render() {
return (
// 这里传下去的 value 就包含了样式与 toggle 样式的方法
<ThemeContext.Provider value={this.state}>
<ThemeTogglerButton />
</ThemeContext.Provider>
);
}
// 在准备放置切换功能的子元件上,取用随着 value 传下来的切换的功能
// * ThemeContext.Consumer 需要放一个函式在子层,这个函式会接收现在的 context,详细 API 解释在下方
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
return (
<ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme !
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;
情境二:如何我有很多个 context,会打架吗?
比较复杂的网站可能会有很种类型的 Global 状态,为了不要拖累 contect re-rendering 的速度,比较好的做法是让每个 context 区分开来。
如果有些 context 常常被一起使用,你也可以考虑自己设计一个可以同时提供二者的 render prop component(见之後的文章)。
// 假设我有一组共用样式、一组共用的使用者状态,我需要分别为他们建立 context
const ThemeContext = React.createContext('morning');
const UserContext = React.createContext({
identity: 'Guest',
});
class App extends React.Component {
render() {
// 在最上层的父元素,将他们一并往下传,采用层层包覆的写法
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
// 这里是会使用到资料的元件位置
// 分别取到值之後,传进子元件 <Avatar> 内
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<Avatar user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
情境三:context 的陷阱,以及避开的方法
Context 如何辨识何时该 re-render context 内容?靠的是 reference,所以有时候也会有一些意料之外的情况,当 provider 的父层 re-render 时,意外带动下层的所有 consumers(订阅与取用 context 的地方)一起 re-render,因为当父层在 re-render 的时候,会为了 value 不断重新建立新的物件。
关於辨识 value 值改变的方法,可以参考 Object.is() 以及下方 API 有更完整的说明。
class App extends React.Component {
render() {
return (
// 当 App re-render 的时候,value 的物件也不断被重新建立
<MyContext.Provider value={{something: 'something'}}>
<Layout />
</MyContext.Provider>
);
}
}
如何解决这种问题?把 value 存放进父层的 state 里,就可以被保留住。
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
// 把 value 里的物件存放在这里,就不会随着 re-render 被洗掉重建
value: {something: 'something'},
};
}
render() {
return (
// 将 state 直接往下传
<MyContext.Provider value={this.state.value}>
<Toolbar />
</MyContext.Provider>
);
}
}
Context API - 了解 API 运作方式
const MyContext = React.createContext(defaultValue);
createContext 会建立一个 context 物件,当 React render 订阅了这个 context 的元件後,会去拿到当下的 context 值(从最接近的上层 Provider 取得)。
createContext 会接收一个参数 defaultValue,只有当元件没有相对应的上层 Provider 时才会用到。如果在 Provider 内传入 undefined,并不会导致子元件吃到 defaultValue。
<MyContext.Provider value={/* some value */}>
每个被建造出来的 context 物件,都会有其相对应的 Provider,让底下的子元件可以订阅 context 的改变。
Provider 元件会接受一个 value 值,value 值会被传递给底下需要使用的元件,一个 Provider 可以有很多个需求方。同样 Provider 也有可能是很多层、但离子元件越远的资讯、会被更靠近的覆盖。
当 value 改变的时候,在 Provider 底下,并且有使用到 context 的元件都会一起 re-render,但这个行为并不会被 shouldComponentUpdate
方法捕捉到。provider 是如何判断 value 的改变?他会去比较旧值与新值,使用跟 Object.is() 相同的演算法。
class MyClass extends React.Component {
render() {
let value = this.context;
}
}
MyClass.contextType = MyContext;
class.contextType 可以被指派给我们建立出来的 context。使用这个 property,你可以在 class component 内使用 this.context 取得现在的 value,跟底下的 Context.Consumer 不同的是,你可以在生命周期方法里,使用 this.context 的值。
不过使用这个方法,你只能订阅一个 context。
// 例如这个例子
function ThemeTogglerButton() {
return (
<ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
Consumer 也会去订阅 context 的改变,需要一个函示放在他的底下,会接受现在的 context 并回传一个 React node。不同之处在於可以让你在 function component 内订阅 context。
如果你使用的是 Hook,请使用 useContext
const value = useContext(MyContext);
useContext 会接收 React.createContext 建立出来的 context 物件,并且 return 当下的 context value,相当於上面提到的 contextType
& Consumer
,不过虽然可以取代这两个,你还是需要 Provider 把 value 向下传。
<<: Day 13【连动 MetaMask - Back-End Services】这显然是厂商的疏失
>>: 找LeetCode上简单的题目来撑过30天啦(DAY10)
前言 除了开发新功能,开会占据了我们许多时间,如果能够节省时间出来,我们才能去学些新技术、重构、甚至...
882. Reachable Nodes In Subdivided Graph https://l...
储存训练好的模型 今日学习目标 使用 pickle + gzip 储存模型 将训练好的模型打包并储存...
Hi,搭给贺,我是Charlie! 在Day01当中,我们安装了所需的套件跟软件,还做了版面。 接下...
今日kata 原始题目如下:(4kyu) Given an n x n array, return ...