在上一章节中,我们介绍了 Context 的使用简介与语法。
React context 的使用环绕三个角色在运作:
另外,我们也介绍了以下几个 context 语法:
React.creatContext
Context.Provider
Context.Consumer
Class.contextType
Context.displayName
在下一章中,我们将介绍一些 context 的范例以及使用技巧。
现在,让我们使用实际的范例来了解 context 如何使用。先来了解一下这个范例要达到哪些需求。
现在要做一个小页面,此页面可以设定 UI theme。页面中有两个 button:
我们如下实作需求:
首先先定义 theme 的种类,这个 app 中会有 light
与 dark
两种 theme
另外,而此页面的预设 theme 为 dark
:
const themes = {
light: {
background: "#eeeeee",
color: "#000000"
},
dark: {
background: "#222222",
color: "#ffffff"
}
};
const ThemeContext = React.createContext(
themes.dark // default value
);
创建 context 的语法为 React.createContext()
,参数带入 theme.dark
代表预设的 theme。
接着制作一个可以随着 theme 的变化而改变的 button:
class ThemedButton extends React.Component {
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{ backgroundColor: theme.background, color: theme.color }}
/>
);
}
}
ThemedButton.contextType = ThemeContext;
ThemeButton
采用 Class.contextType
语法,将要使用的 context 设定为 ThemeContext
。
因为要把 button 的背景与字体颜色都根据 context 的内容调整,因此在 ThemeButton
中用 this.context
的语法取得 theme 并设为 button 的 backgroundColor
与 color
。
最後就来实作 Toolbar
与最重要的 App
component:
// An intermediate component that uses the ThemedButton
function Toolbar(props) {
return (
<div className="toolbar">
<ThemedButton onClick={props.changeTheme}>Change Theme</ThemedButton>
</div>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light
};
this.toggleTheme = () => {
this.setState((state) => ({
theme: state.theme === themes.dark ? themes.light : themes.dark
}));
};
}
render() {
// The ThemedButton button inside the ThemeProvider
// uses the theme from state while the one outside uses
// the default dark theme
return (
<div>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<ThemedButton>Outside Button</ThemedButton>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Toolbar
职责很简单,只显示一个外框,并负责传递 changeTheme
的事件。
App
component 则负责:
Provider
将 Toolbar
以下的 component 定为 light
themeToolbar
与外层的 buttontoggleTheme
读者也可以到 CodePen 上参考完整范例。
从结果上可以看到,Toolbar
内的 button 使用了 Provider 提供的 theme context light
,它会随着 theme 的切换而改变颜色。相较之下,Toolbar
外的 button 没有 Provider 包覆,取得的 theme context 会是预设值 dark
,其颜色不会因为 Provider 的 context 值改变而有所变动。
接下来来学习如何从巢状内的 component 中触发 context 更新。
读者应该会有一个疑问,context 确实可以将值不透过中间层的 component 就传递到底层的 component,然而如果底层的 component 要触发更新时要怎麽办呢?是否还是要将 context 更新函式也透过每一个中间层 component 传下去呢?这样是否就失去 context 的作用了呢?
让巢状内层的 component 也可不经过中间层 component 就触发更新的方法其实很简单,把 context 更新函式也设为 context 内容之一即可。
也就是如果要让 context 可以被内层 component 改变的话,context 就要有以下的内容:
接着就让我们来看看实际范例。
接续刚刚的范例,我们有一个可以设定 UI theme 的页面,然而此页面只会有一个可以随着 UI theme 而改变颜色的 button。
但在程序面有另一个需求:
Toolbar
不用传递 toggleTheme
prop 也能够让最下层的 button 可以改变 theme。实作方式如下,此范例会根据上个范例的内容修改,只会记录有改动的部分:
首先,修改 ThemeContext
:
// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
theme 的内容也是一样会有 light
与 dark
,预设的 theme 也是 dark
。
然而不同的是,因为要让下层的 button 不经过中间层 component 就可以改变 theme,因此额外把 context 更新函式 toggleTheme
也加到 context 中。
接着修改 ThemeButton
:
class ThemeTogglerButton extends React.Component {
render() {
let props = this.props;
// The Theme Toggler Button receives not only the theme
// but also a toggleTheme function from the context
let { theme, toggleTheme } = this.context;
return (
<button
{...props}
onClick={toggleTheme}
style={{ backgroundColor: theme.background, color: theme.color }}
>
Toggle Theme
</button>
);
}
}
ThemeTogglerButton.contextType = ThemeContext;
现在,button 除了从 context 拿 theme
以外,还会顺便拿 toggleTheme
函式。
因为功能增加的关系,把原本的 ThemeButton
改名为 ThemeTogglerButton
。
最後修改 App
component:
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState((state) => ({
theme: state.theme === themes.dark ? themes.light : themes.dark
}));
};
// State also contains the updater function so it will
// be passed down into the context provider
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme
};
}
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Toolbar />
</ThemeContext.Provider>
);
}
}
App
component 唯一修改的部分就是把 state 加上 toggleTheme
後一并传给 ThemeContext.Provider
,这样在 Provider 内的 Consumer ThemeTogglerButton
就可以同时收到 theme 的值与 theme 的更新函式了。
读者也可以到 CodePen 查看完整范例。可以看到按下 button 时,theme context 依然可以正常切换。
在一些需求下,一个 component 可能会要使用多个 context。
举例来说,一个 Header component 可能会同时需要使用 theme 与 user 的内容。
那实作上,要如何让一个 component 使用多个 context 呢?
如果要在一个 component 中使用多个不同的 context 只要巢状使用 Context.Consumer 的语法即可。
举例来说:
function Page() {
return (
<ThemeContext.Consumer>
{(theme) => (
<UserContext.Consumer>
{(user) => <Content user={user} theme={theme} />}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
要注意的是,Class.contextType
语法只能让 component 使用一个 context 而已,因此无法支援使用多个 context 的需求。
接着就让我们来看看实际应用吧!
现在的需求是要做一个页面,此页面会有 Header 在最上方,且 Header 要有以下功能:
实作方式如下,首先定义 App 的 context:
// Theme context, default to light theme
const ThemeContext = React.createContext(themes.dark);
// Signed-in user context
const UserContext = React.createContext({
name: "Guest"
});
会有两个 context:
ThemeContext
:代表 UI theme 的设定,预设为 dark
UserContext
:代表使用者资讯,内容只有 name
属性而已,预设为 Guest
接着就是制作显示元件 Profile
与 Header
:
function Profile({ user, theme }) {
return (
<div
style={{
backgroundColor: theme.background,
color: theme.color
}}
>
{user}
</div>
);
}
// A component may consume multiple contexts
function Header() {
return (
<ThemeContext.Consumer>
{(theme) => (
<UserContext.Consumer>
{(user) => <Profile user={user} theme={theme} />}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
Profile
的职责很简单,就是根据 props 显示对应的颜色与使用者。
而 Header
就是负责同时使用 ThemeContext
与 UserContext
的地方。
可以看到,因为巢状的使用 context,所以最内层的 Profile
可以同时拿到 theme 与 user 的内容,这就达到要在一个 component 中使用两个 context 的需求了。
最後是 App
component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
signedInUser: "Henry"
};
}
render() {
const { signedInUser, theme } = this.state;
// App component that provides initial context values
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Header />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
App
则要负责两件事:
因为现在的 context 有 theme 与 user,因此在 state 中指定完这两个 context 的值後,就要让这些 context 值分别放到 ThemeContext.Provider
与 UserContext.Provider
中,才可让更下层的 Consumer 使用 context。
读者也可以到 CodePen 上参考完整范例。
另外需要注意的是,如果有两组 context consumer 经常一起使用,则可以考虑使用 Render Props 的技巧封装成一个 component,让重用性更高。
还记得在上一章节中提过,只要 Provider 的 value
改变了,则底下的使用此 Provider 的 Consumer component 必定全部 re-render。
因此在使用 context 时应该避免在 Provider value
中直接新物件。如果每次 render
时,value
的值都是新的物件的话,就会导致下层的所有 Consumer 在也都跟着一起 re-render。在 Consumer 数量多的时候,这会造成很大的问题。
范例如下:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
可以看到每次执行 render
时,MyContext.Provider
的 value
都会是一个新的物件(就算实际上内容没有改变),而这会导致 Toolbar
里的 consumer 也随着 App
的 re-render 而重新渲染。
这个问题的解法就是把 Provider value
搬到 state 中。
如此一来,就算 App
重新 render 了,state 的 instance 依然会是一样的,如此就可以避免 Provider 改值导致 Consumer 也一起 re-render 的问题了。
解法范例如下:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
在这个章节中,我们用透过范例学习了 context 的实际使用范例与技巧,内容包括:
最後也提到了使用 context 时应该避免 inline 的把值宣告在 Provider 的 value 上,否则可能产生大量Consumer 非预期 re-render 的行为。
>>: Day [28] Azure 认知服务-Custom Vision 建置
接下来讲讲後续说明会用到的专案建立方式 主控台建立 (Framework4.7.2) 1.开启Vis...
前言:介绍完了二元树的建立和走访方式,紧接着要来介绍其他基本应用,一样用上一篇的程序码进行修改 可以...
嗨,我是 A Fei,让我们看看今天的题目: (题目来源:Codewars) You probabl...
医生说我很健康真是太好了呢,今日题目如下 **题号:2 标题:Add Two Numbers 难度:...
接下来的几篇,我们来看看网路中的协议到底规范了哪些东西,为什麽要有这些规则?又有何优缺点? 首先来看...