• React Context 的使用


      使用React开发应用程序时,如果多个组件需要共享数据,可以把数据放到父组件中,通过属性向下传递给子组件。但当组件嵌套较深时,两个最底层的组件要想共享数据,那就霜要把数据放到最顶层的组件中,然后再一层一层向下传递,可能需要向下传递好多层才能到达想要数据的子组件,这就产生了一个问题,由于经过的这些层(组件)可能不需要这个数据,在向下传递的过程中,有可能就忘记写共享属性,程序也就出错了,并且还不好调试。有没有一种方法,可以穿透组件,想要数据的子组件直接获取到最顶层组件的共享数据, 这就是React context。

      Context就是上下文,环境的意思,React 的context 就是提供了一个上下文或环境,在这个环境中,有context提供者和context消费者,提供者(provider) 提供共享数据,消费者(consumer) 消费这些共享数据。怎么提供和消费呢?provider和consumer是两个组件,provider有一个value属性, 只要把共享的数据放到value中,然后再把consumer组件包起来,consumer组件就能获到到共享数据。consumer组件消费数据的方式有两种,传统的是组件内部是回调函数,现代一点的是useContext hooks。

      create-react-app react-context 创建项目 ,cd react-context && npm install bootstrap。j假设Header中有一个user下拉框,选择user,然后Body中显示user,胡乱编造的一个例子。

       App.js

    import React from "react";
    import 'bootstrap/dist/css/bootstrap.css';  // 添加bootstrap 样式
    
    import Header from "./Header";
    import Details from './Details';
    
    export default function App() {
        return (
            <div className="card">
                 <div className="card-header">
                    <Header></Header>
                </div>
                <div className="card-body">
                    <Details></Details>
                </div>
            </div>
        )
    }

      Header.js  

    import React from "react";
    import UserPicker from "./UserPicker";
    
    export default function Header() {
        return (
            <div className="container">
                <div className="row">
                    <div className="col-sm">
                        <button type="button" className="btn btn btn-light">Booking</button>
                        <button type="button" className="btn btn btn-light">BookList</button>
                    </div>
                    <div className="col-sm">
                        <UserPicker></UserPicker>
                    </div>
                </div>
            </div>
        )
    }

      UserPicker.js

    const users = ['', 'sam', 'jason'];
    
    export default function UserPicker() {
    
        // onChange={handleSelect}
        return (
            <select value={'sam'}>
                {users.map(name => (
                    <option key={name} value={name}>{name}</option>
                ))}
            </select>
        );
    }v

      Detials.js

    const booking = [
        {
          "name": 'sam',
          "title": "Meeting Room",
        },
        {
          "name": 'json',
          "title": "Lecture Hall",
        }
    ]
    
    
    export default function Details() {
    
      return (
        <div className="booking-details" style={{marginLeft: '120px'}}>
          <h5 className="card-title"> Booking Details </h5>
          <p className="card-text">{booking[0].name} {booking[0].title}</p>
        </div>
      );
    }

      UserPicker和Details共享user数据,且在UserPicker中,可以选择user。因此在最外层的组件App中,要设一个user状态和改变user的setUser,让这两个数据共享。使用React.createContext()创建一个context,它接受一个可选的参数,就是共享数据的默认值,当然可以不传,直接调用createContext() 方法。不过,建议写上参数,把它看作是共享数据的格式定义,一看到这个context, 就知道要共享什么。那创建的context 放在什么地方呢?context 可以定义在任意位置,在src 目录下新建一个文件UserContext.js 来存放context.  

    import React from 'react';
    
    export const UserContext = React.createContext({
        user: '',
        setUser: () => {}
    })

      使用provider(<UserContext.Provider />组件)来提供共享数据,在App.js中,使用userState,然后把user和setUser放到Provider的value属性中,最后使用Provider把UserPicker和Details组件包起来

    import { UserContext } from './UserContext';
    
    export default function App() {
        const [user, setUser] = useState('');
    
        return (
            <UserContext.Provider value={{user, setUser}}>
                <div className="card">
                    <div className="card-header">
                        <Header></Header>
                    </div>
                    <div className="card-body">
                        <Details></Details>
                    </div>
                </div>
            </UserContext.Provider>
        )
    }

      UserPicker使用共享数据,最开始的时候,是使用Consumer组件。Consumer 组件的内容是一个函数表达式,函数的参数,就是Consumer组件帮我们注入到组件中的共享数据,它这时就可以直接使用共享数据了。

     
     import { UserContext } from './UserContext';
    export default function UserPicker() {
        return (
            <UserContext.Consumer>
                {(context) => {
                    const {user, setUser} = context;
                    return (
                        <select value={user} onChange={(e) => setUser(e.target.value)}>
                            {users.map(name => (
                                <option key={name} value={name}>{name}</option>
                            ))}
                        </select>
                    )
                }}
            </UserContext.Consumer>
        );
    }

      Details中使用共享数据

    import { UserContext } from './UserContext';
    
    export default function Details() {
    
      return (
        <UserContext.Consumer>
          { (context) => {
            const { user } = context;
            const seleted = booking.filter(data => data.name === user);
    
            return (
              <div className="booking-details" style={{ marginLeft: '120px' }}>
                <h5 className="card-title"> Booking Details </h5>
                <p className="card-text">
                  {seleted.length > 0 &&
                    <React.Fragment>
                      {seleted[0].name} {seleted[0].title}
                    </React.Fragment>
                  }
                </p>
              </div>
            )
          }}
        </UserContext.Consumer>
      );
    }

      如果使用的是React 16.8以上,可以使用useContext hooks,UserPicker.js

    import { useContext } from 'react';
    import { UserContext } from './UserContext';
    
    export default function UserPicker() {
        const {user, setUser} = useContext(UserContext);
    
        return (
            <select value={user} onChange={(e) => setUser(e.target.value)}>
                {users.map(name => (
                    <option key={name} value={name}>{name}</option>
                ))}
            </select>
        );
    }

      Details.js

    import { useContext } from 'react';
    import { UserContext } from './UserContext';
    
    export default function Details() {
    
      const { user } = useContext(UserContext);
      const seleted = booking.filter(data => data.name === user);
    
      return (
        <div className="booking-details" style={{ marginLeft: '120px' }}>
          <h5 className="card-title"> Booking Details </h5>
          <p className="card-text">
            {seleted.length > 0 &&
              <React.Fragment>
                {seleted[0].name} {seleted[0].title}
              </React.Fragment>
            }
          </p>
        </div>
      )
    }

      这也会带来一个问题,当user 改变的时候,App所有子组件都会重新渲染,能不能只渲染consumer组件?要使用children属性,把要获取数据的子组件作为children传递给Provider. 定制一个Provider组件,接受children作为参数。UserContext.js

    import React from 'react';
    import {createContext, useState} from "react";
    
    export const UserContext = createContext({
        user: '',
        setUser: () => {}
    })
    
    export const UserProvider = ({children}) => {
        const [user, setUser] = useState('');
    
        return <UserContext.Provider value={{user, setUser}}>
            {children}
        </UserContext.Provider>
    }

      App.js

    import { UserProvider } from './UserContext';
    
    export default function App() {
    
        return (
            <UserProvider>
                <div className="card">
                    <div className="card-header">
                        <Header></Header>
                    </div>
                    <div className="card-body">
                        <Details></Details>
                    </div>
                </div>
            </UserProvider>
        )
    }

      当在userPicker中调用setUser改变user时,UserProvider组件会重新渲染,但是UserProvider的children不会重新渲染,因为children并没有改变,只有消费context的组件(children)才会重新渲染。UserProvider是从props中获取children, 更新组件内部的state并不会改变props。当后代组件中调用setUser, children的identity并不会改变。它和状态改变以前是同一个object,所以没有必要重新渲染children. 但context的consumer 并不一样,当Provider的值改变时,consumer 会重新渲染。Any components that consume the context, however, do re-render in response to the change of value on the provider, not because the whole tree of components has re-rendered.

      但使用object作为context的值,好不好?是不是把所有的共享数据都放到一个对象中?

    value = {
        theme: "lava",
        user: 'sam',
        language: "en"
    };
    <Context.Provider value={value}><App/></Context.Provider>

      在子组件中,有的组件消费theme, 有的组件消费user, 有的组件消费language。如果value中有一个值改变,所有consuer都会重新渲染,没有必要。可以使用多个provider

    <ThemeContext.Provider value="lava">
        <UserContext.Provider value="sam">
            <LanguageContext.Provider value="en">
                <App />
            </LanguageContext.Provider>
        </UserContext.Provider>
    </ThemeContext.Provider>

      子组件,可以只想获取自己想要的值。

    function InfoPage(props) {
        const theme = useContext(ThemeContext);
        const language = useContext(LanguageContext);
        return (/* UI */);
    }
    
    function Messages(props) {
        const theme = useContext(ThemeContext);
        const user = useContext(UserContext);
        // subscribe to messages for user
        return (/* UI */);
    }

      定制一个AppProvider

    function AppProvider({ children }) {
        // maybe manage some state here
        return (
            <ThemeContext.Provider value="lava">
                <UserContext.Provider value="sam">
                <LanguageContext.Provider value="en">
                    {children}
                </LanguageContext.Provider>
        </UserContext.Provider>
        </ThemeContext.Provider >
        );
    }

      使用它

    <AppProvider>
        <App/>
    </AppProvider>

       如果再仔细一点,user和setUser可以作两个provider

    import { createContext, useState } from "react";
    
    export const UserContext = createContext({
        user: ''
    })
    export const UserSetContext = createContext({
        setUser: () => { }
    });
    
    export function UserProvider({ children }) {
        const [user, setUser] = useState(null);
    
        return (
            <UserContext.Provider value={user}>
                <UserSetContext.Provider value={setUser}>
                    {children}
                </UserSetContext.Provider>
            </UserContext.Provider>);
    }

       有时,也不一定非要使用Context,React官网还提供了组件组合

    export default function App({ user }) {
      const { username, avatarSrc } = user;
    
      return (
        <main>
          <Navbar username={username} avatarSrc={avatarSrc} />
        </main>
      );
    }
    
    function Navbar({ username, avatarSrc }) {
      return (
        <nav>
          <Avatar username={username} avatarSrc={avatarSrc} />
        </nav>
      );
    }
    
    function Avatar({ username, avatarSrc }) {
      return <img src={avatarSrc} alt={username} />;
    }

      只有在App组件中知道Avatar组件的属性,可以直接创建一个Avatar组件向下传递

    export default function App({ user }) {
      const { username, avatarSrc } = user;
    
      const avatar = <img src={avatarSrc} alt={username} />;
    
      return (
        <main>
          <Navbar avatar={avatar} />
        </main>
      );
    }
    
    function Navbar({ avatar }) {
      return <nav>{avatar}</nav>;
    }
  • 相关阅读:
    注意
    被虐的很惨
    在cmd中可以运行java,但是不能用javac
    Linux常用命令
    安装JDK和eclipse
    重装win7
    小希的迷宫
    并查集——The Suspects
    BFS宽度优先搜索
    括号匹配
  • 原文地址:https://www.cnblogs.com/SamWeb/p/16003839.html
Copyright © 2020-2023  润新知