zoukankan      html  css  js  c++  java
  • react-Context

    我们先来看一个示例

    import React from 'react'
    class Demp extends React.Component{
      render () {
        return <Toolbar theme="dark"></Toolbar>
      }
    }
    
    function Toolbar(props){
      return (
        <div>
          <ThemeButton theme={props.theme}></ThemeButton>
        </div>
      )
    }
    function Button (props) {
      return (
        <button className={props.theme}>{props.theme}</button>
      )
    }
    class ThemeButton extends React.Component{
      render () {
        return <Button theme={this.props.theme}></Button>
      }
    }
    
    export default Demp
    

    从代码中我们可以发现,我们通过props逐级传送,最终实现我们所要的效果。我们知道在react中数据是通过props属性自上而下(由父及子)进行传递的,但这种做法,对某些组件来说就相对繁琐,这些属性是应用程序中许多组件都需要的。Context提供了一种组件之间共享此类值的方式。而不必显示的通过组件数逐层传递props

    何时使用Context

    Context的设计目的是为了共享那些对于一个组件树而言是"全局"的数据,例如当前认证的用户、主题或者首选语言,在上面示例中我们通过theme属性来调整组件的样式

    而使用了context后,我们可以避免中间元素传递props

    import React from 'react'
    // Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
    // 为当前的 theme 创建一个 context(“light”为默认值)。
    const ThemeContext = React.createContext('light')
    class Demo extends React.Component{
      render () {
        return(
         // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
        // 无论多深,任何组件都能读取这个值。
        // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
          <ThemeContext.Provider value="dark">
            <Toolbar />
          </ThemeContext.Provider>
        )
      }
    }
    function Toolbar () {
      return(
        <div>
          <ThemeButton />
        </div>
      )
    }
    function Button (props) {
      return (
        <button className={props.theme}>{props.theme}</button>
      )
    }
    class ThemeButton extends React.Component{
     // 指定 contextType 读取当前的 theme context。
      // React 会往上找到最近的 theme Provider,然后使用它的值。
      // 在这个例子中,当前的 theme 值为 “dark”。
      static contextType = ThemeContext
      render(){
        console.log(this)
        return <Button theme={this.context} />
      }
    }
    export default  Demo
    

    使用context之前考虑

    context主要应用场景在于很多不同层级的组件需要访问同样数据。请谨慎使用,因为这样会使得组件复用性变差

    如果你只想避免层层传递一些属性,组合组件有时候是一个比context好的一个解决方案

    比如考虑一个这样的Page组件,它层层向下传递user和avatarSize属性,从而深度嵌套的Link和Avatar组件可以读取这两个属性

    <Page user={user} avatarSize={avatarSize} />
    // ... 渲染出 ...
    <PageLayout user={user} avatarSize={avatarSize} />
    // ... 渲染出 ...
    <NavigationBar user={user} avatarSize={avatarSize} />
    // ... 渲染出 ...
    <Link href={user.permalink}>
      <Avatar user={user} size={avatarSize} />
    </Link>
    

    如果在最后,只有Avatar组件真的需要user和avataSize这两个属性,那么层层传递这两个属性就显得比较冗余,而且,一旦Avatar组件需要更多来自顶层组件的props,你还得在中间层级一个个加上去,这就会比较麻烦

    一种无需用context的方法是将Avatar组件自身传递下去,因此中间组件无需知道user或者avatarSize等props

    function Page(props) {
      const user = props.user;
      const userLink = (
        <Link href={user.permalink}>
          <Avatar user={user} size={props.avatarSize} />
        </Link>
      );
      return <PageLayout userLink={userLink} />;
    }
    
    // 现在,我们有这样的组件:
    <Page user={user} avatarSize={avatarSize} />
    // ... 渲染出 ...
    <PageLayout userLink={...} />
    // ... 渲染出 ...
    <NavigationBar userLink={...} />
    // ... 渲染出 ...
    {props.userLink}
    

    这种变化下,只有最顶上的Page组件需要知道Link和Avatar是如何需要user和avatarSize的

    这种对组件的控制反转减少了在你的应用中要传递的 props 数量,这在很多场景下会使得你的代码更加干净,使你对根组件有更多的把控。但是,这并不适用于每一个场景:这种将逻辑提升到组件树的更高层次来处理,会使得这些高层组件变得更复杂,并且会强行将低层组件适应这样的形式,这可能不会是你想要的。

    而且你的组件并不限制于接收单个子组件。你可能会传递多个子组件,甚至会为这些子组件(children)封装多个单独的“接口(slots)”,正如这里的文档所列举的

    function Page(props) {
      const user = props.user;
      const content = <Feed user={user} />;
      const topBar = (
        <NavigationBar>
          <Link href={user.permalink}>
            <Avatar user={user} size={props.avatarSize} />
          </Link>
        </NavigationBar>
      );
      return (
        <PageLayout
          topBar={topBar}
          content={content}
        />
      );
    }
    

    这种模式足够覆盖很多场景了,在这些场景下你需要将子组件和直接关联的父组件解耦。如果子组件需要在渲染前和父组件进行一些交流,你可以进一步使用 render props。

    但是,有的时候在组件树中很多不同层级的组件需要访问同样的一批数据。Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据,这比替代方案要简单的多。

    API

    React.createContext

    const myContext = React.createContext(defaultValue)
    

    创建一个context对象,当React渲染一个订阅了这个context对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

    只有当组件所处的树中没有匹配到Provider时,defaultValue才会失效这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

    Context.Provider

    <myContext.Provider value={某个值} />
    

    每个Context对象都会返回一个Provider React组件,它允许消费组件订阅context的变化。

    Provider接收一个value属性,传递给消费组件,一个Provider组件可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据

    当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

    class.contextType

    class MyClass extends React.Component {
      componentDidMount() {
        let value = this.context;
        /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
      }
      componentDidUpdate() {
        let value = this.context;
        /* ... */
      }
      componentWillUnmount() {
        let value = this.context;
        /* ... */
      }
      render() {
        let value = this.context;
        /* 基于 MyContext 组件的值进行渲染 */
      }
    }
    MyClass.contextType = MyContext;
    

    挂载在class上的contextType属性会被重新赋值为一个由React.createContext('defaultValue)创建的context对象。这能让你使用this.context来消费最近的context上的那个值,你可以在任何生命周期里访问到它,包括render函数

    class MyClass extends React.Component {
      static contextType = MyContext;
      render() {
        let value = this.context;
        /* 基于这个值进行渲染工作 */
      }
    }
    
    

    Context.Consumer

    <MyContext.Consumer>
      {value => /* 基于 context 值进行渲染*/}
    </MyContext.Consumer>
    

    这里,React组件也能订阅到context的更新,这能让你在函数组件中完成订阅context

    这需要函数作为子元素这种做法。这个函数接收当前的context值,返回一个react节点。传递个函数的value值等同于往上组件树离这个context最近的Provider提供的value值,如果没有Provider,那么value值等同于创建context对象的defaultValue值

    Context.displayName

    context 对象接受一个名为 displayName 的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。

    示例,下述组件在 DevTools 中将显示为 MyDisplayName:

    const MyContext = React.createContext(/* some value */);
    MyContext.displayName = 'MyDisplayName';
    
    <MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
    <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
    

    示例

    动态的Context

    const themes = {
      light:{
        foreground:'#000000',
        background:"#eee"
      },
      dark:{
        foreground:'#ffffff',
        background:'#222'
      }
    }
    const ThemeContext = React.createContext(themes.dark)
    class ThemedButton extends React.Component{
      render (){
        console.log(this.props,this.context)
        let props = this.props
        let theme = this.context
        return(
          <button {...props} style={{backgroundColor:theme.background}}></button>
        )
      }
    }
    ThemedButton.contextType = ThemeContext
    
    function Toolbar(props){
      console.log(ThemedButton)
      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 () {
        // 在 ThemeProvider 内部的 ThemedButton 按钮组件使用 state 中的 theme 值,
        // 而外部的组件使用默认的 theme 值
        console.log(this.state.theme)
        return(
          <div>
            <ThemeContext.Provider value={this.state.theme}>
              <Toolbar changeTheme={this.toggleTheme}></Toolbar>
            </ThemeContext.Provider>
            <ThemedButton>change</ThemedButton>
          </div>
        )
      }
    }
    export default App
    

    在嵌套组件中更新Context

    从一个在组件树中嵌套很深的组件中更新context是很有必要的。在这种场景下,你可以通过context传递一个函数,使得consumer组件更新context

    const themes = {
      light:{
        foreground:'#000',
        background:'#eee'
      },
      dark:{
        foreground:'#fff',
        background:'#222'
      }
    }
    const ThemeContext = React.createContext({
      theme:themes.dark,
      toggleTheme:() => {}
    })
    function ThemeTogglerButton(){
      return(
        <ThemeContext.Consumer>
          {
            ({theme,toggleTheme})=> (
              <button onClick={toggleTheme} style={{ backgroundColor: theme.background }}>Toggle Theme</button>
            )
          }
        </ThemeContext.Consumer>
      )
    }
    class App extends React.Component{
      constructor (props) {
        super(props);
        this.toggleTheme = () =>{
          this.setState(state => ({
            theme:state.theme === themes.dark?themes.light:themes.dark
          }))
        }
        this.state = {
          theme:themes.light,
          toggleTheme:this.toggleTheme
        }
      }
      render(){
        return(
          <ThemeContext.Provider value={this.state}>
            <Content></Content>
          </ThemeContext.Provider>
        )
      }
    }
    function Content(){
      return(
        <ThemeTogglerButton></ThemeTogglerButton>
      )
    }
    
    export default App
    

    消费多个content

    为了确保content快速渲染,React需要使每一个consumer组件的context在组件树中成为单独的一个节点

    // Theme context,默认的 theme 是 “light” 值
    const ThemeContext = React.createContext('light');
    
    // 用户登录 context
    const UserContext = React.createContext({
      name: 'Guest',
    });
    
    class App extends React.Component {
      render() {
        const {signedInUser, theme} = this.props;
    
        // 提供初始 context 值的 App 组件
        return (
          <ThemeContext.Provider value={theme}>
            <UserContext.Provider value={signedInUser}>
              <Layout />
            </UserContext.Provider>
          </ThemeContext.Provider>
        );
      }
    }
    
    function Layout() {
      return (
        <div>
          <Sidebar />
          <Content />
        </div>
      );
    }
    
    // 一个组件可能会消费多个 context
    function Content() {
      return (
        <ThemeContext.Consumer>
          {theme => (
            <UserContext.Consumer>
              {user => (
                <ProfilePage user={user} theme={theme} />
              )}
            </UserContext.Consumer>
          )}
        </ThemeContext.Consumer>
      );
    }
    
  • 相关阅读:
    java ArrayList存储基本类型
    java ArrayList的几种方法使用
    java ArrayList的基本使用
    java 猜数字
    java Random随机生成一个数
    java Scanner输入数字、字符串
    java 构造方法
    java this的应用
    java pravite关键字的使用
    云计算服务
  • 原文地址:https://www.cnblogs.com/dehenliu/p/13367740.html
Copyright © 2011-2022 走看看