Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
使用Context 前的考虑
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
但是,有的时候在组件树中很多不同层级的组件需要访问同样的一批数据。Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据,这比替代方案要简单的多。
motivation
problem
属性需要层层传递。
class App extends React.Component {render() {return <Toolbar theme="dark" />;}}function Toolbar(props) {// highlight-range{1-4,7}// The Toolbar component must take an extra "theme" prop// and pass it to the ThemedButton. This can become painful// if every single button in the app needs to know the theme// because it would have to be passed through all components.return (<div><ThemedButton theme={props.theme} /></div>);}class ThemedButton extends React.Component {render() {return <Button theme={this.props.theme} />;}}
solution
通过 static contextType 解决。
// highlight-range{1-4}// Context lets us pass a value deep into the component tree// without explicitly threading it through every component.// Create a context for the current theme (with "light" as the default).const ThemeContext = React.createContext('light');class App extends React.Component {render() {// highlight-range{1-3,5}// Use a Provider to pass the current theme to the tree below.// Any component can read it, no matter how deep it is.// In this example, we're passing "dark" as the current value.return (<ThemeContext.Provider value="dark"><Toolbar /></ThemeContext.Provider>);}}// highlight-range{1,2}// A component in the middle doesn't have to// pass the theme down explicitly anymore.function Toolbar(props) {return (<div><ThemedButton /></div>);}class ThemedButton extends React.Component {// highlight-range{1-3,6}// Assign a contextType to read the current theme context.// React will find the closest theme Provider above and use its value.// In this example, the current theme is "dark".static contextType = ThemeContext;render() {return <Button theme={this.context} />;}}
multiple-contexts
// Theme context, default to light themeconst ThemeContext = React.createContext('light');// Signed-in user contextconst UserContext = React.createContext({name: 'Guest',});class App extends React.Component {render() {const {signedInUser, theme} = this.props;// App component that provides initial context values// highlight-range{2-3,5-6}return (<ThemeContext.Provider value={theme}><UserContext.Provider value={signedInUser}><Layout /></UserContext.Provider></ThemeContext.Provider>);}}function Layout() {return (<div><Sidebar /><Content /></div>);}// A component may consume multiple contextsfunction Content() {// highlight-range{2-10}return (<ThemeContext.Consumer>{theme => (<UserContext.Consumer>{user => (<ProfilePage user={user} theme={theme} />)}</UserContext.Consumer>)}</ThemeContext.Consumer>);}
theme-detailed
app
import {ThemeContext, themes} from './theme-context';import ThemedButton from './themed-button';// An intermediate component that uses the ThemedButtonfunction Toolbar(props) {return (<ThemedButton onClick={props.changeTheme}>Change Theme</ThemedButton>);}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() {//highlight-range{1-3}// The ThemedButton button inside the ThemeProvider// uses the theme from state while the one outside uses// the default dark theme//highlight-range{3-5,7}return (<Page><ThemeContext.Provider value={this.state.theme}><Toolbar changeTheme={this.toggleTheme} /></ThemeContext.Provider><Section><ThemedButton /></Section></Page>);}}ReactDOM.render(<App />, document.root);
themed-button
import {ThemeContext} from './theme-context';class ThemedButton extends React.Component {// highlight-range{3,12}render() {let props = this.props;let theme = this.context;return (<button{...props}style={{backgroundColor: theme.background}}/>);}}ThemedButton.contextType = ThemeContext;export default ThemedButton;
theme-context
export const themes = {light: {foreground: '#000000',background: '#eeeeee',},dark: {foreground: '#ffffff',background: '#222222',},};// highlight-range{1-3}export const ThemeContext = React.createContext(themes.dark // default value);
updating-nested
App
import {ThemeContext, themes} from './theme-context';import ThemeTogglerButton from './theme-toggler-button';class App extends React.Component {constructor(props) {super(props);this.toggleTheme = () => {this.setState(state => ({theme:state.theme === themes.dark? themes.light: themes.dark,}));};// highlight-range{1-2,5}// State also contains the updater function so it will// be passed down into the context providerthis.state = {theme: themes.light,toggleTheme: this.toggleTheme,};}render() {// highlight-range{1,3}// The entire state is passed to the providerreturn (<ThemeContext.Provider value={this.state}><Content /></ThemeContext.Provider>);}}function Content() {return (<div><ThemeTogglerButton /></div>);}ReactDOM.render(<App />, document.root);
theme-toggler-button
import {ThemeContext} from './theme-context';function ThemeTogglerButton() {// highlight-range{1-2,5}// The Theme Toggler Button receives not only the theme// but also a toggleTheme function from the contextreturn (<ThemeContext.Consumer>{({theme, toggleTheme}) => (<buttononClick={toggleTheme}style={{backgroundColor: theme.background}}>Toggle Theme</button>)}</ThemeContext.Consumer>);}export default ThemeTogglerButton;
context
// Make sure the shape of the default value passed to// createContext matches the shape that the consumers expect!// highlight-range{2-3}export const ThemeContext = React.createContext({theme: themes.dark,toggleTheme: () => {},});
