在上一章节中,我们介绍了何谓 context。
Context 是一种利用向下广播来传递资料的方式,此方法可以解决 props 必须要一层层向下传递的缺点。
而根据 React 官方推荐,我们应该只在要将全域性资料(e.g. 使用者资讯、时区设定、语系、UI 主题 ...etc)向下传递给很多底层 component 时才应该使用 context。
如果是要避免中间层 component 传递过多细节 props 的话,开发者可以使用 composition 的技巧,把整个下层 component 作为 props 传递下去即可。
在本章节中,我们将介绍 context 的详细语法。
在开始前,先让我们来概述一下使用 context 的概念。
React context 的使用会环绕三个角色在运作:
一个 React app 中可以有多个 React context。每个 React context 的本体都是一个物件(在这边把它称为 context object)。其中 context object 中又会有两个很重要的属性:Provider(提供者)与 Consumer(消费者)。
使用 Provider 的 component 与使用 Consumer 的 component 之间不需要是直接的父子层关系。Provider 只要在 Consumer 的上层即可让 Consumer 接收到 context 值,而处於 Provider 与 Consumer 之间的中间层 component 则不须做任何的改动。
根据以上的资讯,我们可以把使用 Context 归纳成以下几个步骤:
接下来,就让我们进入介绍语法的篇章吧!
首先要先学习如何创建一个 React context。
创建 React context 需使用 React.createContext
这个函式。
更详细一点说明,这个函式有两个功能:
语法如下所示:
const MyContext = React.createContext(defaultValue);
React.createContext
需要带入一个参数:
defaultValue
:代表这个 context 的预设值,与 props 一样,可为任意的值
因为代表预设值,所以只有在 Consumer 以上的 component 中都没有 Provider 时才会使用到 defaultValue
的内容。
需要注意的是,如果 Consumer 上面有 Provider,但此 Provider 的值为 undefined
的话,则 Consumer 依然不会使用 defaultValue
,拿到的值会是 undefined
。
React.createContext
会回传一个值:
Context object:也可以说是 context type,会是一个 JavaScript object
在下一个段落中会更详细的介绍 context object。
接着要介绍 Context object。
如刚刚所讲,Context object(Context type)会是一个 JavaScript object。每个 Context object 中会有两个很重要的属性:
Provider
Consumer
把 context object 的 log 下来的话,即可看到这两个属性:
console.log(MyContext);
// {$$typeof: Symbol(react.context), Consumer: {$$typeof: Symbol(react.context), _context: {…}, …}, Provider: {$$typeof: Symbol(react.provider), _context: {…}}, _calculateChangedBits: null, _currentValue: 123, _currentValue2: 123, _threadCount: 0, …}
读者也可以到 CodePen 上的 console 查看内容。
Provider
与 Consumer
的详细内容将在下面介绍。
每个 Context 物件中都会有 Provider
属性,其用途就是将指定的值传给更下层的 Consumer
使用。
Context.Provider
是一个 React element,因此可以使用 JSX 表示。语法如下:
<MyContext.Provider value={/* some value */}>{/* ... */}</MyContext.Provider>
Context.Provider
可以带入一个 prop:
value
:代表提供给 Provider 以下的 Consumer 使用的值,与 props 一样,可为任意的值。
换句话说,Provider 提供了 value
後,更下层的 component 都可以用 Context.Consumer
接收 value
的内容。
Context.Provider
中可以再包裹 Context.Provider
。
当巢状使用同一个 Provider 时,内层的 Provider 会遮蔽掉(覆盖,而非 Merge)外层 Provider 的 value
。
也就是说内层Provider 以下的 component 会拿到内层的 value
;介於内外层 Provider 之间的 component 则还是会拿到外层 Provider 的 value
。
读者可以查看此 CodePen 范例。
需要注意的是,一旦 Context.Provider
的 value
改变,则所有使用此 Provider 以下的 Customer component 都会 re-render。
此 re-render 不会因为 shouldComponentUpdate
为 false 而取消,也不会因为 Consumer 以上的中间层的 component 没有更新而不执行。也就是这些 Consumer component "必定" 会 re-render。
因此在使用 Context.Provider
时需要注意不要 inline 赋值给 value
,否则当使用 Provider 的 component re-render 时,使用此 Provider 的 Consumer 都会一并 re-render。这将造成极大的效能问题。
Object.is
来判断至於 Context.Provider
的 value
改变与否则是使用 Object.is 来判断。
Object.is
基本上就是 ===
的概念,只是额外增加了 NaN
与 ±0
的判断而已。
Object.is
的详情资讯请参考 MDN 的介绍。
每个 Context 物件中都会有 Consumer
属性,其用途就是接收上层 Provider
传下来的值。
Context.Consumer
是一个 React element,因此可以使用 JSX 表示。语法如下:
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
// {$$typeof: Symbol(react.element), type: {…}, key: null, ref: null, props: {…}, …}
Context.Consumer
可以带入一个 prop:
children
:代表要 render 的内容,会是一个 function
需要注意的是,children
是一个 function 而非 React element,如果 children
不是带入 function 的话,React 就会报警告。
使用 function 当作 children 是 Render props 的技巧,此技巧将在之後章节介绍。
children
function 接受一个参数:
value
:代表 Consumer 接收到的值,与 props 一样,可能为任意的值。而 children
function 会回传一个值:
React element
:即代表要 render 出来显示在画面上的内容Consumer 接收到的值会是由最靠近自己的 Provider 所提供的。
也就是,如果有巢状的 Provider 时,Consumer 拿到的值会是靠最内层 Provider 所提供的,较外层 Provider 的值会被遮蔽掉。
举例来说:
const MyContext = React.createContext({ theme: "default" });
const consumer = (
<MyContext.Provider value="outerProvider">
<MyContext.Provider value="innerProvider">
<MyContext.Consumer>{(value) => <div>{value}</div>}</MyContext.Consumer>
</MyContext.Provider>
</MyContext.Provider>
);
ReactDOM.render(consumer, document.getElementById("root"));
范例中,有两个 Provider 包裹一个 Consumer。Consumer 拿到的会是最靠近自己的 Provider 所提供的值 "innerProvider"
,而非外层 Provider 提供的 "outerProvider"
。
如果读者想要自己试试的话可以参考这个 CodePen 范例。
React class component 可以接受名为 contextType
的属性。此属性的用处与 Context.Consumer
相同,都是用来接收上层 Provider
传下来的值。
但与 Context.Consumer
不同,使用 Class.contextType
会分为以下两个步骤:
因为 class 本身并不会知道开发者要使用的 context 为何,因此要先指定 context object。
设定要使用的 context 的语法如下:
class MyClass extends React.Component {
// ...
}
MyClass.contextType = MyContext;
如果专案有支援实验性 public class fields syntax 语法的话,也可使用 static
设定 contextType
,两种语法效果相同:
class MyClass extends React.Component {
static contextType = MyContext;
// ...
}
因为是设定 context,因此 contextType 只能接受以下型别:
Context object:即代表要使用的 context
如果 contextType
的内容不是 context object 的话,则 React 会报警告。
MyClass.contextType = 123;
// Warning: TestContextType defines an invalid contextType. contextType should point to the Context object returned by React.createContext().
接着来到取用 context 内容的部分。
如果要使用 contextType
方式取用 context 内容的话,则需要在 class 内使用 this.context
:
class MyClass extends React.Component {
// ...
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
this.context
的内容与 Context.Consumer
接收到的值相同,都代表 Provider 传下来的 value
。
另外,this.context
的特性与 Context.Consumer
相同,都是接收最靠近自己的 Provider 的值。
this.context
可以在 lifecycle 函式中取用另外一点与 Context.Consumer
不同的地方是,this.context
可以在所有 lifecycle 函式中取用,而不只限制在 render
函式中使用而已。
也就是说,commit 阶段的 lifecycle 函式(e.g. componentDidMount
、componentDidUpdate
...etc)就可以取用 this.context
的值来执行各种 side-effect:
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
如上面段落所提及,Class.contextType
与 Context.Consumer
功能相同,都是用来取用最靠近自己的 context 值,然而它们之间还是有些区别,如下所示:
从可以使用的 component 种类来看
Context.Consumer
可以在 class component 与 function component 中使用Class.contextType
则只能在 class component 中使用从可以取用 context 的位置来看
Context.Consumer
因为是 React element,所以只能在 render
函式中使用Class.contextType
则可以在各种 lifecycle 函式中使用,因此可以支援取用 context 值执行 side-effect从可以使用的 context 数量来看
Context.Consumer
外还可以再包其他的 Consumer,因此一个 component 可以使用多种 context objectClass.contextType
因为语法的限制,一个 class component 只能指定使用一种 context object最後,context object 可以支援使用 displayName
属性,可让 React dev tool 上的 context 显示为 displayName
设定的名称:
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider /> // "MyDisplayName.Provider" in React DevTools
<MyContext.Consumer /> // "MyDisplayName.Consumer" in React DevTools
因为是设定名称,displayName
只能接受以下型别:
如果不设定 displayName 的话,context 预设的 displayName
就是 "Context"。
在有多个 context object 的状况下,如果都没设定 displayName
就会很难在 React dev tool 上区别各个 context,如下所示:
const MyContext = React.createContext(/* some value */);
const MyOtherContext = React.createContext(/* some value */);
<MyContext.Provider /> // "Context.Provider" in React DevTools
<MyContext.Consumer /> // "Context.Consumer" in React DevTools
<MyOtherContext.Provider /> // "Context.Provider" in React DevTools
<MyOtherContext.Consumer /> // "Context.Consumer" in React DevTools
因此建议读者在使用 context 时都尽量设定 displayName
。
本章节介绍了 React context 的用法。
React context 的使用会环绕三个角色在运作:
使用 Provider 的 component 与使用 Consumer 的 component 之间不需要是直接的父子层关系,只要 Provider 在 Consumer 的上层即可让 Consumer 接收到 context 值。
另外,我们也介绍了以下几个 context 语法:
React.creatContext
Context.Provider
Context.Consumer
Class.contextType
Context.displayName
在下一章中,我们将介绍一些 context 的范例以及特殊使用技巧。
>>: Day 27 Filebeat with multiple module and ELK Dashboard
网页的开始 於布局排版 现在的年代 也需要RWD适合部分版型 所以我们就由布局开始吧 常常会看到一种...
紧接着昨天~ 我们写了一个func 并且利用结构加入阵列的方式写入每个变数的字串以及图片。 而後在生...
今天来说明一下,在Ruby的世界里,运算符代表什麽意思? 之前偶然间在等候区,和同学们讨论这个问题,...
首先要做这个议题其实也是来自专案的过程的经验。以前听到客户要我们埋GA并提供给我们一段追踪码,就也搞...
不太可能每个专案都那麽爽,可以把相片储存在内部储存空间/Android/data/packageNa...