函数式组件
是个函数,不能访问this对象,也就不存在state、实例方法、钩子、也不需要,只能访问props,无需实例化,渲染性能高,适用场景:展示,纯渲染的地方,别名:UI组件,哑组件,无状态组件,木偶组件
const 组件名=(props)=>(jsx)
const 组件名=props=>jsx
const 组件名=(props)=>{
//业务
return jsx
}
组件通讯
父子
//单项数据流
<Child 属性=数据/>
this.props.属性
子父
//反向数据流
<Child 属性=父方法/>
this.props.属性(子数据)
中间人
<ChildA 属性=父方法/>
<ChildB 属性=接受的a数据/>
所有 React 组件都必须是纯函数,并禁止修改其自身 props
纯函数:不改变输入参数,输出可控,必须有返回值
refs
<子组件 ref={el=>this.inputRef=el} />
//父组件
this.inputRef.子属性|state|props|instanceMethod 子父
context组件上下文
Context 旨在共享一个组件树内可被视为 “全局” 的数据,达到越级传递,场景:当前经过身份验证的用户,主题或首选语言,包括管理当前的 locale,theme,或者一些缓存数据
老api
//顶层组件 类属性 组件属性 定义提供上文
static childContextTypes={//类型
msg: propTypes.string,
setMsg : propTypes.func
};
getChildContext(){//提供上文
return {
msg:this.state.msg,
setMsg:this.setMsg
}
}
//下层组件 类属性 接受下文
static contextTypes = {
msg: propTypes.string,
setMsg: propTypes.func
};
//使用
this.context.msg | this.context.setMsg(数据)
新api
//Context
import {createContext} from 'react'
const Context = createContext(默认值);//默认值可以不给
export default Context
//祖先组件 Context.Provider包裹组件并且传递属性值
import Context from './Context';
class 祖先组件 extends Component {
state = {
count: 60
};
render() {
const { count } = this.state;
return (
<Context.Provider value={count}>
...
<中间件层组件 />
...
</Context.Provider>
);
}
}
//后代组件 Context.Consumer来接收值,Consumer里面不能直接渲染其他组件,而是要声明一个函数。函数的参数就是context的值
import Context from './Context';
export default class Leaf extends Component {
render() {
return (
<Context.Consumer>
{
value => {
return (
<div className="leaf">
{value}
</div>
)
}
}
</Context.Consumer>
)
}
}
//封装Context.Provider
import React,{Component} from "react";
import Context from './Context'
export default class Provider extends Component {
state={
count:10
...
};
increment=(val=1,ev)=>this.setState({count:this.state.count+val})
decrement=(val=1,ev)=>this.setState({count:this.state.count-val})
...
render(){
return (
<Context.Provider value={
{
count: this.state.count,
increment: this.increment,
decrement: this.decrement
}
}>
{this.props.children}
</Context.Provider>
)
}
}
//使用封装
<Provider>
<App/>
</Provider>
订阅发布
pub/sub模式、 消息通知、观察者模式、yarn add pubsub-js -D
- 订阅: token=pubsub.subscribe(‘消息名’,回调函数(‘消息名’,数据))
- 发布: pubsub.publish(‘消息名’,数据)
- 清除指定订阅:pubsub.unsubscribe(token|‘消息名’|回调函数名);
- 清除所有:pubsub.unsubscribeAll()
路由
let {history,location,match}=props
import {widthRoute}='react-router-dom'
web存储
localStrage、cookie
状态管理
后面学习
高阶组件 HOC
又叫Higher-Order Components,是一个函数能够接受一个组件并返回一个新的组件。组件是将props转化成UI,然而高阶组件将一个组价转化成另外一个组件,例如react-router-dom的widthRouter
就是一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件,withRouter函数就是一个高阶组件,它返回了一个新的组件,这个组件具有了路由信息。
const withRouter = WrappedComponent => {
.... 抓取到history,location,match
return props => <WrappedComponent history={history} {...props} />;
//return 要求是个类或者函数
};
const Swiper = props => (
<div class="user-container">
<p>My name is {props.history}!</p>
</div>
);
export default withRouter(Swiper);
渲染属性(Render Props)
render prop 是一个用于告知组件需要渲染什么内容的函数,React 组件之间使用一个值为函数的 prop 共享代码的简单技术
class Mouse extends React.Component{
mouseOver = () => {console.log('over')};
mouseOut = () => {console.log('out')};
render(){
return (
<div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
{this.props.render()}
</div>
)
}
}
//
<Mouse render={()=>{
return (
<>
<h3>标题</h3>
<p>段落</p>
<p>段落</p>
<p>段落</p>
</>
)
}}/>
状态管理
- 思想:flux
- 实现:vuex redux
redux
可以同一个地方查询状态,改变状态,传播状态,用在中大项目,组件状态需要共享,在任何地方都可以拿到,组件需要改变全局状态,一个组件需要改变另外一个组件的状态,创建store实例,其他组件导入并共享这个store实例
redux成员
成员 | 作用 | 类型 |
---|---|---|
createStore | 创建store实例 | 函数 |
combineReducers | 合并多个reducer | 函数 |
applyMiddleware | 安装中间件,改装增强redux | 函数 |
compose | 增强调试开发环境 | 函数 |
store成员
成员 | 作用 | 类型 |
---|---|---|
subscribe | 订阅state变化 | 函数 |
dispatch | 发送action 给 reducer | 函数 |
getState | 获取一次state的值 | 函数 |
replaceReducer | 一般在 Webpack Code-Splitting 按需加载的时候用 | 函数 |
数据流动
component(views) | action | reducer | state | component(views) |
---|---|---|---|---|
转发的动作 | 同步业务处理逻辑, 返回copy更新后的state | 状态收集 | 展示state | |
store.dispatch—》 | --------》 | 《– | 《–subscribe | |
《– | 《–getState |
操作流程
import {createStore} from 'redux'
//生成默认state
let defaultState={}
//创建reducer
const reducer = (state=defaultState,action)=>{
let {type,payload}=action
swtich type
case XXXXX
更新copy后的state Object.assign(空,老,新)
default:
return state
}
//创建store对象
store = createStore(reducer,state)
export default store;
//组件内部更新,状态获取state
import store from '...'
store.dispatch({type:xxx,payload:ooo}) //发送action给reducer type是必传参数
store.subscribe(回调) //订阅 state 更新state时触发
store.getState() //获取状态,执行一次
提取并定义 Action Creators
let nextTodoId = 0;
export const addTodo = text => ({
type: "ADD_TODO",
id: nextTodoId++,
text
});
export const removeTodo = id => ({
type: "REMOVE_TODO",
id
});
export const checkNav = bl => ({
type: "CHECK_NAV",
bl
});
//处理异步
const updateHome = (collectionName) => dispatch => { //dispatch接受函数 需要thunk中间件
return axios.get({api:collectionName}).then(
res=> {
dispatch({type:'UPDATE_HOME',payload:res.data.data});
return res//有回执
}
)
};
//安装中间件改装 redux 目标:dispatch可以接受一个函数
import {createStore,applyMiddleware,combineReducers} from 'redux'
import thunk from 'redux-thunk'
let store = createStore(rootReducer,rootState,applyMiddleware(thunk));
//组件内部
dispatch(checkNav(!bNav))
dispatch(addTodo('呵呵哒'))
combineReducers提取reducer
当应用逻辑逐渐复杂的时候,我们就要考虑将巨大的 Reducer 函数拆分成一个个独立的单元,这在算法中被称为 ”分而治之“,Reducers 在 Redux 中实际上是用来处理 Store 中存储的 State 中的某个部分,一个 Reducer 和 State 对象树中的某个属性一一对应,一个 Reducer 负责处理 State 中对应的那个属性
// src/plugins/redux
import {createStore,applyMiddleware,combineReducers} from 'redux'
import thunk from 'redux-thunk'
import todos from '../store/reducers/todos'
import bNav from '../store/reducers/nav'
let rootReducer=combineReducers({bNav,todos});
let store = createStore(rootReducer,applyMiddleware(thunk));
export default store;
// src/store/reducers/todos
let initState=[]
const todos = (todos=initState, action) => {
switch (action.type) {
case "ADD_TODO": {
return [
...todos,
{
id: action.id,
text: action.text,
completed: false
}
]
}
case "REMOVE_TODO": {
const { id } = action;
todos.map((item,index) => item.id ===id && todos.splice(index, 1));
return [...todos]
}
case "CHECK_TODO": {
const { id } = action;
todos.map((item,index) => item.id ===id && (todos[index].completed=!todos[index].completed));
return [...todos]
}
default:
return todos;
}
};
export default todos;
// src/store/reducers/bNav
const bNav = (bNav=false, action) => {
switch (action.type) {
case "CHECK_NAV": {
const { bl } = action;
return bl
}
default:
return bNav;
}
};
export default bNav;
state数据不写在构造器内订阅,可以写在主入口文件 订阅react-dom的更新
let render = ()=>{
ReactDOM.render(
<App/>,
document.getElementById('root')
)
};
render();
store.subscribe(render);
react-redux
基于redux思想,专门为react使用redux而生,把组件拆分为容器组件, UI组件,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它
UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
容器组件
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
最佳实现
//主入口
import {Provider} from react-redux
import store from './plugins/redux'
<Provider store={redux打造的store}>
<根组件/>
</Provider>
//Creators改装 把异步actins内部有关,api请求的通用部分封装出来的一个过程
//api
const get = ({api,_page=1,_limit=10,id=null}) => (
axios({
url: id ? `/mock/${api}/${id}` : `/mock/${api}`,
params: {_page,_limit}
})
);
//actionsCreators
const clearHome={type: 'CLEAR_HOME'};//dispatch接受对象 默认
const updateHome = () => dispatch => { //dispatch接受函数 需要thunk中间件
return get({api:'home'}).then(
res=> {
dispatch({type:'UPDATE_HOME',payload:res.data.data});
return res//有回执
}
)
};
const updateBANNER=()=>async dispatch => {
let res = await get({api:'banner'});
dispatch({type:'UPDATE_BANNER',payload:res.data.data})
};
export {clearHome,updateHome,updateBANNER}
//UI组件
const Home = ({home, banner,dispatch}) => {
useEffect(() => {
dispatch(clearHome);
dispatch(updateHome()).then(data => 收取回执)
dispatch(updateBANNER())
}, []);
return (
<div className="Home">
<Swiper data={banner}/>
{
home.map(item => (
<Cell key={item.id} item={item} dataName="home"/>
))
}
</div>
)
};
//容器组件 dispatch方法 默认传递给UI组件
export default connect(
state=>({banner:state.banner, home:state.home})
)(Home)
redux-devtools使用
import {createStore,combineReducers,applyMiddleware,compose} from 'redux';
//compose 增强器
import thunk from 'redux-thunk'
let rootReducer = combineReducers({banner, column, detail, follow, home, user});
//使用redux-devtools
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
let store = createStore(rootReducer,composeEnhancers(applyMiddleware(thunk)));//安装了中间件,改装了redux
export default store;
token加入redux 做持久化处理
思路1:登录、注销、强刷同步redux,axios拦截器只读redux,为了速度,跳转有axios完成, 问题是组件会渲染,再去跳转
思路2:redux里面准备一条数据,axios和其他组件都去修改他 , 或使用它,无需跳转,顶层准备一个BaseLayout布局组件,返回的界面根据这条数据响应式渲染
//BaseLayout.jsx
export const connect(user=> state.user)(function BaseLayout({user:{err}, ...rest}){
if (err==1) { return <Login {...rest} /> }
if (err==3) { return <Reg /> }
return <default {...rest}/>
})
思想3: 前置路由守卫(部分路由独享),负责跳转
片段
为一个组件返回多个根级元素。 可以让你将多个子元素列表添加到一个分组中,可以接受业务逻辑,并且不会在DOM中增加额外节点
<React.Fragment key="bmw"></..>
<></>
//只接受 key` and `children 属性
异步组件
把静态导入的组件,变成一个可以返回promise的函数,函数内部在路由跳转时,去异步加载目标组件,关键字import(),create-react-app 环境 webpack自动分片打包
//import 语法
import ("./ChildB.js").then(
ChildB=>console.log(ChildB.Default) // new ChildB.Default() 实例化
)
//方式1
const Follow = asyncComponent(()=>import("./Follow"))
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null
};
}
async componentDidMount() {
const { default: component } = await importComponent();
this.setState({
component: component
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
//方式2
import Loadable from 'react-loadable';
const Loading = () => <div>Loading...</div>;
const Home = Loadable({
loader: () => import('./routes/Home'),
loading: Loading,
loading:()=>{return null}
});
PureComponent
- 使用PureCompoent是因为它是一个更具性能的Component的版本
- 性能的提高还伴随着一些附加的条件
- 提供了具有浅比较的shouldComponentUpdate方法
- 当props或者state改变时,PureComponent将对props和state进行浅比较
- Component的shouldComponentUpdate构造被调用默认重渲,PureCompoent不一定
- 不能再重写shouldComponentUpdate
- 不渲染的情况: 父组件中改变对象的子键,子组件比较的是引用是否相同,
- 不要在
render
方法中创建一个新的函数,对象或者是数组 - 场景:组件收到的props和定义的state是基本类型时,复合类型更新时修改根键时
UI库
Ant Design
antd
是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。
特性
- 🌈 提炼自企业级中后台产品的交互语言和视觉风格。
- 📦 开箱即用的高质量 React 组件。
- 🛡 使用 TypeScript 开发,提供完整的类型定义文件。
- ⚙️ 全链路开发和设计工具体系。
- 🌍 数十个国际化语言支持。
- 🎨 深入每个细节的主题定制能力。
安装
yarn add antd --save
按需引入
yarn add babel-plugin-import --save
webpack loader配置,找到babel-loader 按需引入配置 394行
+ options 项目
"plugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }],
// `style: true` 会加载 less 文件 pc
]
使用组件
import {LocaleProvider, DatePicker,Button } from 'antd';
修改文案
// 方案1 V3
import zhCN from 'antd/lib/locale-provider/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
//组件需要被 包裹
<LocaleProvider locale={zhCN}>
<App/>
</LocaleProvider>
//方案2 V4
import zhCN from 'antd/es/locale/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
return (
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
);
栗子***
antd-mobile
安装
yarn add antd-mobile --save
按需引入
yarn add babel-plugin-import --save
//webpack loader配置,找到babel-loader 按需引入配置
// + options 项目
"plugins": [
["import", { libraryName: "antd-mobile", style: "css" }]
// `style: true` 会加载 less 文件 touch pc端配置和touch端配置不可并存
]
使用组件
import { DatePickerView } from 'antd-mobile'; //直接使用组件 文案是中文
//import 'antd-mobile/lib/DatePickerView/style/css'; 手动
栗子
//Tabbar组件
//TabBar>TabBar.Item + 数据(title,key,path,icon,selectedIcon)
//路由:
history.push(this.state.tabs[index].path)
//监听:
static getDerivedStateFromProps(nextProps,nextState) {}
location.pathname.indexOf(item.path)
setState->selectedTab:item.key
//home
Flex 组件
Flex>Flex.Item style={{flex:0.6}} 约定比例
WhiteSpace 上下留白
Carousel 走马灯
Link>img
Grid 宫格
Tabs 标签页
//category
分段器手写 + Route
//follow/column
PullToRefresh 拉动刷新
List 列表
List.Item history.push(编程式跳转)
List.Item.Brief
//detail
NavBar导航
箭头样式 写入base.css 覆盖默认,同类共用
WingBlank 两侧留白
Flex>Flex.Item
//shopcart
SwipeAction 滑动操作
List>SwipeAction>List.Item>Stepper步进器
//user
卡片 card
Card>Card.Header|Body>Badge 徽标
Card>Card.Header|Body>Grid 宫格
NoticeBar 通告栏
//登录|注册
InputItem 文本输入
Button 按钮
行间样式修改 覆盖样式
mobx
一款可以与redux媲美的数据流方案,Flux思想单向数据流方案,以 Redux 为代表,Reactive响应式数据流方案,以 Mobx 为代表
- 单向数据流实现:redux + react-redux + react-thunk + redux-saga
- 响应式数据流实现:mobx + mobx-react
MobX 的理念是通过观察者模式对数据做出追踪处理,在对可观察属性作出变更或者引用的时候,触发其依赖的监听函数,整体的store注入机制采用react提供的context来进行传递
适用场景可以是react vue angular mpvue 小程序 taro
装饰器Decorator
是个函数,用来装饰类或者类成员 ,是Object.defineProperty的语法糖
//给对象添加或修改属性
Object.defineProperty(target, prop, desc)
//target 需要定义属性的当前对象
//prop 当前需要定义的属性名 类型:字符
desc | 默认值 | 说明 |
---|---|---|
configurable | false | 描述属性是否可以被删除,默认为 false |
enumerable | false | 描述属性是否可以被for…in或Object.keys枚举,默认为 false |
writable | false | 描述属性是否可以修改,默认为 false |
get | undefined | 当访问属性时触发该方法,默认为undefined |
set | undefined | 当属性被修改时触发该方法,默认为undefined |
value | undefined | 属性值,默认为undefined |
//定义装饰器
function 装饰器名 (target,prop,descriptor){
descriptor.writable=false;//writable属性是否可以写入
return descriptor;
}
//使用
@装饰器名 类
@装饰器名 类的实例属性|静态属性
@装饰器名 类的实例方法|静态方法
@装饰器名(参数) 类的实例属性|静态属性
装饰器名(类的实例属性|静态属性)
//使用场景
mobx / angluarTs / vueTs / reactTs / java ...
配置
cra脚手架 不支持装饰器语法,需要小配一下
yarn add @babel/plugin-proposal-decorators --save
package.json
babel: {
"presets":...
+
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
]
....
}
vscode编辑配置
vscode->设置->搜索设置输入:experimentalDecorators->勾上
//webstrom 无需设置
mobx成员
//mobx 6.0 -
import {observable,action} from 'mobx'
@observable 装饰store类的成员,为被观察者
@action 实例方法, 处理实例属性,修改状态,不推荐组件内部改
//mobx 6.0 + 变更 无需装饰器成员
import {makeAutoObservable} from 'mobx'
constructor(){
makeAutoObservable(this);
}
mobx-react成员
inject
observer
Provider
Provider,顶层提供store的服务
<Provider store={store} key={store.key}></Provider>
inject,注入Provider提供的store到该组件的props中,组件内部使用,inject 是一个高阶组件 高阶组件返回的是组件,作用在包装组件
//注入所有内容
export default inject('store')(组件)
//注入指定内容
export default inject(
stores => ({key:stores.xx.oo})
)(组件)
@inject 是装饰器,装饰的是类本身和类成员
@inject('store') class 类组件
observer,设置当前组件为观察者,一旦检测到store中被监测者发生变化就会进行视图的强制刷新
@observer class 类组件
const 函数式组件=observer((store)=>{jsx})
构建
程序主入口
import {Provider} from 'mobx-react'
import store from './store';
<Provider store={store}>所有</.>
store
// src/store/index
import User from './user'
...
class Store {
constructor(){
this.user = new User(this);//传递this防止this丢失
.... 可以把组件的数据交给一个一个类来处理,有的模块管理的感觉
}
}
export default new Store();
// src/store/user
import { observable, action } from 'mobx'
import axios from "axios";
class User {
//被观测者
@observable user= window.localStorage.getItem('1909_newsapp') ?
JSON.parse(window.localStorage.getItem('1909_newsapp')) :
{
err:1,
msg:'未登录',
data:{}
};
constructor(store){
this.store=store;
}
//处理被观测者数据
@action check = async ({api,method='get',username,password}) => {
return axios({
url:`/api/${api}`,
method,
params: method === 'get' ? {username, password}: null,
data: method === 'post' ? {username, password}: null,
}).then(
res=>{
this.user = res.data;
window.localStorage.setItem('xxx',JSON.stringify(res.data));
return res
}
)
};
}
export default User;
//组件注入 被做一个观察者
import {inject, observer} from "mobx-react";
@inject('store')
@observer
export default class Home extends React.Component{
constructor(props){
super(props);
props.store.goods.update({
...
})
}
render(){
let {goods:{home,banner}}=this.props.store;
return(
...
)
}
}
//面对函数式组件
const react函数式组件=observer((store)=>{jsx})
export default inject('store')(react函数式组件)
hooks 钩子
Hook 使你在非 class 的情况下可以使用更多的 React 特性,React为什么要搞一个Hooks,想要复用一个有状态的组件太麻烦了!我们都知道react都核心思想就是,将一个页面拆成一堆独立的,可复用的组件,并且用自上而下的单向数据流的形式将这些组件串联起来。但假如你在大型的工作项目中用react,你会发现你的项目中实际上很多react组件冗长且难以复用。尤其是那些写成class的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦,那之前,官方推荐怎么解决这个问题呢?答案是:渲染属性(Render Props)和高阶组件(Higher-Order Components),hooks为共享状态逻辑提供更好的原生途径,使你在无需修改组件结构的情况下复用状态逻辑
版本支持上,16.7.0-alpha 开始支持 16.8.0 第一个正式版
使用规则
- Hook可让您在不编写类的情况下使用状态和其他React功能
- 只能在顶层调用Hooks 。不要在循环,条件或嵌套函数中调用Hook
- 只能在functional component或者自定义钩子中使用Hooks
- 钩子在类内部不起作用,没有计划从React中删除类
useState 状态
import { useState } from 'react';
const [状态变量, 状态方法] = useState(状态属性的初始值);
const [count, setCount] = useState(0);
//使用状态
{状态变量} //返回 状态值
//修改状态
setCount(新值)
可以自由命名,状态变量可以不只一个state变量了
useEffect 生命周期
每当 React更新之后,就会触发 useEffect,在第一次 render 和每次 update 后触发,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
import { useEffect } from 'react';
useEffect(()=>{
//didMount || didUpdate
return ()=>{willUnmount}
},[])
[] == didMount ,不传递==didMount + didUpdate
[state|props] == 指定的state或者props变化时
每一个state|props可以拥有一个effect(关注点分离),按照 effect 声明的顺序依次调用
return 函数,在需要清除副作用时使用
第一个参数(函数),不允许async修饰,不允许返回任何值
useRef 元素引用
返回一个可变的ref对象,current属性初始化为传递的参数initialValue
let refContainer = useRef(initialValue) // ~~ React.createRef(init)
<JSX ref={refContainer} ...
refContainer.current -> dom操作
自定义钩子 useXxxXxx
- 重用不同组件之间的常见有状态业务逻辑。
- 但每次使用自定义钩子时,其中的所有状态和效果都是完全隔离的
- 我必须以“ use” 开头命名我的自定义Hook
- 自定义Hook是一个JavaScript函数,其名称以“ use” 开头,可以调用其他Hook
function useList(initList) {
//使用系统和自定义钩子
let [list, setList] = useState(initList);
//业务
function add(item) {
alert('add')
setList([...list, item])
}
function del(index) {
let arr = [...list];
arr.splice(index, 1);
setList(arr);
}
function check(index, key,value) {
alert('check')
let arr = [...list];
arr[index][key] = value;
setList(arr);
}
// return [list, add, del, check]
return {list, add, del, check}
}
//上面的业务,可以被购物结算和留言列表多个组件复用
useContext
不使用组件嵌套就可以订阅 React 的 Context,useContext(MyContext)
相当于 class 组件中的 static contextType = MyContext
或者 MyContext.Consumer
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Xxx />
</ThemeContext.Provider>
);
}
function Xxx(props) {
return (
<div>
<Ooo />
</div>
);
}
function Ooo() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
...
</button>
);
}