raect——context

1.何时使用context?

使用场景:共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

如下面:Toobar->ThemedButton->Button,传递属性:theme

function ThemedButton(props) {
  return <Button theme={props.theme} />;
}

// 中间组件
function Toolbar(props) {
  // Toolbar 组件必须添加一个额外的 theme 属性
  // 然后传递它给 ThemedButton 组件
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

优化:使用context,避免中间元素传递props:

// 创建一个 theme Context,  默认 theme 的值为 light
const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  // ThemedButton 组件从 context 接收 theme
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

// 中间组件
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

可以在控制台验证。

结论:

(1)当 React 渲染 context 组件 Consumer时,优先从最接近的provider中读取当前的context值。如果上层的组件树没有一个匹配的 Provider,那么你可以从 defaultValue读取其默认值。

(2)不要仅仅为了避免在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件需要访问相同数据的情景。

API

React.createContext
const {Provider, Consumer} = React.createContext(defaultValue);

创建一对 { Provider, Consumer }。

Provider
<Provider value={/* some value */}>

Provider:接受一个value属性,并将它传递给自己的后代Consumers。

Providers 可以被嵌套以覆盖组件树内更深层次的值。

Consumer
<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

用来订阅 context 变化的 React 组件。

接收一个 函数作为子节点. 函数接收当前 context 的值并返回一个 React 节点。

订阅到的value值:优先找组件树中上层 context 的最近的 Provider 的 value 属性,如果没有找到,将从createContext() 的 defaultValue读取value值。

应用

1.动态订阅主题

思路:ThemeContext(通过state绑定)->Toolbar->ThemedButton->button

theme-context.js(声明全局context)

export const themes = {
  light: {
    foreground: '#ffffff',
    background: '#222222',
  },
  dark: {
    foreground: '#000000',
    background: '#eeeeee',
  },
};

export const ThemeContext = React.createContext(
  themes.dark // 默认值
);

themed-button.js

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

function ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <button
          {...props}
          style={{backgroundColor: theme.background}}
        />

      )}
    </ThemeContext.Consumer>
  );
}

export default ThemedButton;

app.js

import {ThemeContext, themes} from './theme-context';
import ThemedButton from './button';

// 一个使用到ThemedButton组件的中间组件
function 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() {
    // ThemedButton 位于 ThemeProvider 内
    // 在外部使用时使用来自 state 里面的 theme
    // 默认 dark theme
    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>
          <Toolbar changeTheme={this.toggleTheme} />
        </ThemeContext.Provider>
        <Section>
          <ThemedButton />
        </Section>
      </Page>
    );
  }
}

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

2.父子耦合

上面例子是:将顶层provider绑定状态值,通过状态值改变,来动态改变context值。

下面讨论场景:从组件树中某个深度嵌套的组件中更新 context,设计思路:通过 context 向下传递一个函数,以允许 Consumer 更新 context 。

如下:ThemeContext(传递可以更新context的函数)->content->ThemeTogglerButton->button(绑定可以更新context的函数)

theme-context.js

// 确保默认值按类型传递
// createContext() 匹配的属性是 Consumers 所期望的
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});

theme-toggler-button.js

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

function ThemeTogglerButton() {
  // Theme Toggler 按钮不仅接收 theme 属性
  // 也接收了一个来自 context 的 toggleTheme 函数
  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;

app.js

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,
      }));
    };
    // State 包含了 updater 函数 所以它可以传递给底层的 context Provider
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // 入口 state 传递给 provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

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

3.作用于多个上下文

当作用于多个上下文时,React 需要使每一个 Consumer 在组件树中成为一个单独的节点。

建议:如果两个或者多个上下文的值经常被一起使用,推荐考虑你自己渲染属性的组件。

// 主题上下文, 默认light
const ThemeContext = React.createContext('light');

// 登陆用户上下文
const UserContext = React.createContext();

// 一个依赖于两个上下文的中间组件
function Toolbar(props) {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App组件提供上下文的初始值
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Toolbar />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

4.在生命周期方法中访问 Context

可以在生命周期中访问context,只需要将它作为一个 props 传递,然后像通常使用 props 一样去使用它。

class Button extends React.Component {
  componentDidMount() {
    // ThemeContext value is this.props.theme
  }

  componentDidUpdate(prevProps, prevState) {
    // Previous ThemeContext value is prevProps.theme
    // New ThemeContext value is this.props.theme
  }

  render() {
    const {theme, children} = this.props;
    return (
      <button className={theme ? 'dark' : 'light'}>
        {children}
      </button>
    );
  }
}

export default props => (
  <ThemeContext.Consumer>
    {theme => <Button {...props} theme={theme} />}
  </ThemeContext.Consumer>
);

5.高阶组件中的context

引入:下面代码,可以产生主题按钮。但是,如果要订阅主题输入框等其他主题组件呢?

const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <button className={theme} {...props} />}
    </ThemeContext.Consumer>
  );
}

使用高阶组件中的context,快速订阅主题组件。即传入普通组件,使用高阶函数withTheme进行封装。

高阶函数:

const ThemeContext = React.createContext('light');

// 在函数中引入组件
export function withTheme(Component) {
  // 然后返回另一个组件
  return function ThemedComponent(props) {
    // 最后使用context theme渲染这个被封装组件
    // 注意我们照常引用了被添加的属性
    return (
      <ThemeContext.Consumer>
        {theme => <Component {...props} theme={theme} />}
      </ThemeContext.Consumer>
    );
  };
}

使用:

function Button({theme, ...rest}) {
  return <button className={theme} {...rest} />;
}

const ThemedButton = withTheme(Button);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值