使用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>;
}

React Context 的使用的更多相关文章

  1. React context基本用法

    React的context就是一个全局变量,可以从根组件跨级别在React的组件中传递.React context的API有两个版本,React16.x之前的是老版本的context,之后的是新版本的 ...

  2. [React] Prevent Unnecessary Rerenders of Compound Components using React Context

    Due to the way that React Context Providers work, our current implementation re-renders all our comp ...

  3. React Context API

    使用React 开发程序的时候,组件中的数据共享是通过数据提升,变成父组件中的属性,然后再把属性向下传递给子组件来实现的.但当程序越来越复杂,需要共享的数据也越来越多,最后可能就把共享数据直接提升到最 ...

  4. 探索 Redux4.0 版本迭代 论基础谈展望(对比 React context)

    Redux 在几天前(2018.04.18)发布了新版本,6 commits 被合入 master.从诞生起,到如今 4.0 版本,Redux 保持了使用层面的平滑过渡.同时前不久, React 也从 ...

  5. React Hooks +React Context vs Redux

    React Hooks +React Context vs Redux https://blog.logrocket.com/use-hooks-and-context-not-react-and-r ...

  6. [译]React Context

    欢迎各位指导与讨论 : ) 前言 由于笔者英语和技术水平有限,有不足的地方恳请各位指出.我会及时修正的 O(∩_∩)O 当前React版本 15.0.1 时间 2016/4/25 正文 React一个 ...

  7. react context跨组件传递信息

    从腾讯课堂看到的一则跨组件传递数据的方法,贴代码: 使用步骤: 1.在产生参数的最顶级组建中,使用childContextTypes静态属性来定义需要放入全局参数的类型 2.在父组件中,提供状态,管理 ...

  8. [译]迁移到新的 React Context Api

    随着 React 16.3.0 的发布,context api 也有了很大的更新.我已经从旧版的 api 更新到了新版.这里就分享一下我(作者)的心得体会. 回顾 下面是一个展示如何使用旧版 api ...

  9. React Context(一):隐式传递数据

    一 Context概述 Context provides a way to pass data through the component tree without having to pass pr ...

  10. react context toggleButton demo

    //toggleButton demo: //code: //1.Appb.js: import React from 'react'; import {ThemeContext, themes} f ...

随机推荐

  1. LVS负载均衡(4)-- LVS FWM防火墙标记

    防火墙标记的作用是:借助于防火墙标记来分类报文,然后基于标记定义集群服务:可将多个不同的应用使用同一个集群服务进行调度. 实现方法: 在Director主机打标记,作用在mangle表的PREROUT ...

  2. 🔥httpsok-v1.8.1 一分钟搞定SSL证书自动续期

    httpsok-v1.8.1 一分钟搞定SSL证书自动续期 简介 一行命令,一分钟轻松搞定SSL证书自动续期 httpsok 是一个便捷的 HTTPS 证书自动续签工具,专为 Nginx 服务器设计. ...

  3. python交教程4:文件操作

    文件操作流程 人类操作一个word流程: 1.找到文件.双击打开 2. 读或修改 3. 保存&关闭 ⽤python操作⽂件也差不多: 只读模式  创建模式  追加模式  遍历文件  图片视频- ...

  4. win10找回Ubuntu启动项(非EasyBCD)

    最近想对装在电脑上的Ubuntu进行更新,但是之前在BIOS里改了引导系统的文件,导致找不到Ubuntu启动项,EasyBCD程序也不起作用(整块硬盘Windows分区都是GPT,改BIOS也没什么用 ...

  5. 运算符优先级 JAVA11

    **运算符优先级 ** 规律:算术运算符优先级较高,关系和逻辑运算符优先级较低.多数运算符具有左结合性,单目运算符.三目运算符.赋值运算符具有右结合性. 运算符的优先级 举例 使用优先级为 1 的小括 ...

  6. 20220314线上panic总结

    panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentatio ...

  7. CSP-S2021 游记

    那天是惊蛰 满天花瓣 就像现在 我看清了无池里的那个人 前言 终于是开了 游记 的坑(期盼已久!) 虽然参加过 CSP-J2019 ,CSP-S2020 以及 NOIP2020 ,但是都没有写过游记( ...

  8. itest(爱测试) 接口测试,敏捷测试管理平台 10.4.0 发布

    一:itest work 简介 itest work 开源敏捷测试管理,包含极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试,接口Mock,还有压测 ,又有丰富的统计分析,8合1工作站.可按 ...

  9. 新手入门html

    网页的组成:结构 表现 行为 Web标准: 结构  表现 行为 Html  css  js Html和css 是w3c制定标准  js是ECMA制定标准 HTML:指的是超文本标记语言 文件命名的规范 ...

  10. css做多列瀑布流

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...