[铁人赛 Day09] React Context(上)-单纯的用法

Why Context

在写 React 网站的时候,因为资料都是由上而下的藉由元件传递,有些情况,例如:整体 UI 主题、使用者身份验证、偏好语言...等广泛应用的资料,就会显得太笨重。同样的资料要同时传递给很多下层元件、而且这些元件可能都在深处,会让 code 更加凌乱且难以阅读。对此,React 提供了一种方法:Context,让资料的分享可以不需要如此繁复。

How To Use Context

Context 的设计理念是用来分享那些在元件树中,称得上 global 的资讯。例如下面的情境,就是当使用者,点选了「黑夜版」的网页:


// 使用 createContext 方法,为目前的网站色调建立 context,并给予一个预设值  
const ThemeContext = React.createContext('morning');

class App extends React.Component {
  render() {
		// 使用 Provider 方法,把上面建立的 context 传递给底下的元件,这里重新赋予新的黑夜版样式 
		// 不论元件埋的多深,都可以读到
    return (
      <ThemeContext.Provider value="night">
        <Menu />
				....里面的 component 都可以收到
      </ThemeContext.Provider>
    );
  }
}

// Menu 作为传递的中间人,不需要有把 props 往下传的行为
function Menu() {
  return (
    <div>
      <MenuButton />
			<MenuButton />
			<MenuButton />
    </div>
  );
}

class MenuButton extends React.Component {
	// 要阅读到我们一开始定义的 ThemeContext,需要把它赋值给 contextTyp
	// 这里的运作是这样子:React 会去找到最接近且吻合的 theme provider 并使用它的值(也就是'night')
  static contextType = ThemeContext;
  render() {
    return <Button color={this.context} />;
  }
}

你要传递下去的资讯,也有可能是一大包,收拢在另外的文件里,如以下的案例,此时,可以这样传递:

///// theme 的定义区块	theme.js
export const themes = {
  morning: {
    fontColor: 'black',
    background: 'light-grey',
		q
  },
  night: {
    fontColor: 'white',
    background: 'blue',
  },
};

export const ThemeContext = React.createContext(
  themes.night
);

///// theme 的使用者 footer.js

import {ThemeContext} from './theme-context';

class ThemeFooter extends React.Component {
  render() {
    let props = this.props;
    let theme = this.context;
    return (
      <Footer
        {...props}
        style={{backgroundColor: theme.background}}
      />
    );
  }
}
// 这里把 import 进来的样式表,塞进元件的 contextType 里
ThemeFooter.contextType = ThemeContext;
export default ThemeFooter;

///// theme 的切换区块	app.js
import {ThemeContext, themes} from './theme';
import ThemeFooter from './footer';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.morning,
    };
  }

  render() {
    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>
					</ThemeFooter>
					//....其他需要使用样式的元件
        </ThemeContext.Provider>
      </Page>
    );
  }
}

ReactDOM.render(<App />, document.root);

什麽情况下不该用 Context ?

过度的使用 context,会让你的元件变得不好复用,如果你单纯只是讨厌把资料传递很多层(而非那些广泛运用的情境),可以考虑别的作法。以下举一个例子:假设网站的 照片、使用者名称、性别... 在最上层取得,需要经过页面 layout、Header、Menu 区域...才能抵达最後会用到资讯的 使用者头像 元件,除了层层传递或者用 context(不建议),还可以如何解决?

解决方法之一是,不要单独传递 props 资讯下去,你可以直接传递一整个使用者头像元件,中间用来传递的元件,就不需要看到那些细碎的使用者资料。

// 这里是最上层取得使用者资讯的地方
function Page(props){
		// 把使用者头像在上层,连同要用到的资讯打包好
  const userAvatar = <Avatar user={user} size={props.avatarSize} />;
	// 再把 使用者头像元件 当成 props 传递下去
  return <PageLayout userAvatar={userAvatar} />;
}

然而,这样的做法也并非适用於每个案例,当我们把 userAvatar 在上层打包好,也意味着会增加上层元件的复杂程度。

这样子的 props 并不局限於只能应用一组资料,你也可以为要传递下去的 props 建立很多个不同的「插槽」(如范例)。如果你传递下去的这些元件,有与上层元件沟通的需求,你可以考虑 Render Props 的方法。

function Page(props){
  const blockContent = <Description user={user} />;
  const header = <Avatar user={user} size={props.avatarSize} />;
	const detail = <Detail infor={props.infor} />;
  return (
    <PageLayout
      header={header}
      content={blockContent}
			moreInfor={detail}
    />
  );
}

以上的案例情境都较为单纯,但实际在使用 context 的时候,情况可能更加复杂,有时候会有多组的 context 需要传递,或者 theme toggle 也需要一并传递到子元件的情况...,请见下篇文章。


<<:  Material UI in React [Day 23] Data Display (part 3) 表格 & 提示

>>:  [Day 12] 决策树 (Decision tree)

月费如何定价?免费试用会提高订阅率吗?

去年因为肺炎导致广告收入骤降,加上iOS的IDFA政策的双重打击,我们决定开始做月费制的功能。 上线...

[Day 15] 针对网页的单元测试(一)

我们之前做的单元测试, 比较接近针对API的测试, 那我们现在要开始针对网页来做测试, 我们首先针对...

Day-1 杰哥的考研纪录

杰哥的考研纪录 tags: IT铁人 首先先跟各位打个招呼! 欢迎来到杰哥的考研小天地~ 这篇会简单...

在 Clear Linux 上安装 VirtualBox 6.1.26

前言 我是先把 Kernel 等相关套件,以及 VirtualBox 安装起来之後,再从错误讯息去尝...

滑鼠键盘的无线世界 - Uifying /蓝芽

朋友送了一组键盘滑鼠.Logitech 键盘yr0009 & 滑鼠M215 想要滑鼠放家里用...