Context使用&redux使用及实现
目标
- 掌握Context
- 掌握redux使用及实现
context:https://react.docschina.org/docs/context.html#consuming-multiple-contexts
redux:https://www.redux.org.cn/
redux git:https://github.com/reduxjs/redux
一、组件跨层级通信 - Context
在一个典型的 React 应用中,数据是通过 props
属性自上而下
(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
React中使用Context
实现祖代组件
向后代组件
跨层级传值。 Vue中的provide & inject
来源于Context。
Context API
React.createContext
:创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
Context.Provider
:Provider 接收一个 value 属性,传递给消费组件,允许消费组件订阅 context 的变化。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
Class.contextType
:挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括render 函数中。
你只通过该 API 订阅单一 context。
Context.Consumer
:这里,React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。
这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给createContext() 的 defaultValue。
使用Context
1、创建context
农民种菜:如果没有匹配到Provider,取值默认值
export const UserContext = React.createContext({ user: "xiuluo" });
2、Provider提供值
接收者:批发商批发菜
注:如果把UserContext.Provider注释掉,MyContextClass和MyContextFunction将取不到user值,取默认值xiuluo
import { UserContext } from "./userContext";
return (
<UserContext.Provider value={{ user: "xiuluogg" }}>
<MyContextClass />
</UserContext.Provider>
);
3、消费者:组件获取数据
(1)、class组件
import { UserContext } from "./userContext";
class MyContextClass extends Component {
// static contextType = UserContext;
render() {
return <div>{this.context.user}</div>;
}
}
MyContextClass.contextType = UserContext;
export default MyContextClass;
效果:
把MyContextClass挂到app上
(2)、函数组件
上面Provide提供值处加上
import MyContextFunction from "./MyContextFunction";
<UserContext.Consumer>
{(ctx) => <MyContextFunction {...ctx} />}
</UserContext.Consumer>
函数组件
export default function MyContextFunction(props) {
return <div>{props.user}</div>;
}
效果
消费多个Context
看消费的组件引入的是哪个context
<UserContext.Provider value={{ user: "xiuluogg" }}>
<MyContextClass />
<UserContext.Consumer>
{(ctx) => <MyContextFunction {...ctx} />}
</UserContext.Consumer>
<ThemeContext.Provider value={{ theme: "blue" }}>
<!-- ==================== -->
<MultipleContextsPage />
<!-- ==================== -->
</ThemeContext.Provider>
</UserContext.Provider>
MultipleContextsPage
render() {
return (
<div>
<ThemeContext.Consumer>
{(theme) => (
<UserContext.Consumer>
{(user) => <div className={theme.themeColor}>{user.name}</div>}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
</div>
);
}
注意事项
因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每次Provider 重渲染时,以下的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象:
class App extends React.Component {
render() {
return (
<Provider value={{ something: "something" }}>
<Toolbar />
</Provider>
);
}
}
为了防止这种情况,将 value 状态提升到父节点的 state 里:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: { something: "something" },
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
总结
在React的官方文档中,Context被归类为高级部分 (Advanced),属于React的高级API,但官方并不建议在稳定版的App中使用Context。
不过,这并不意味着我们不需要关注Context。事实上,很多优秀的React组件都通过Context来完成自己的功能,比如 react-redux的,就是通过Context提供一个 全局态的store,路由组件react-router通过Context管理路由状态等等。在React组件开发中,如果用好Context,可以让你的组件变得强大,而且灵活。
函数组件中可以通过useContext引人上下文
二、Reducer
什么是reducer:https://cn.redux.js.org/docs/basics/Reducers.html
reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
之所以将这样的函数称之为 reducer,是因为这种函数与被传入 Array.prototype.reduce(reducer, ? initialValue)
里的回调函数属于相同的类型。保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如
Date.now()
或Math.random()
。
思考:有如下函数, 聚合成一个函数,并把第一个函数的返回值传递给下一个函数,如何处理。
function f1(arg) {
console.log("f1", arg);
return arg;
}
function f2(arg) {
console.log("f2", arg);
return arg;
}
function f3(arg) {
console.log("f3", arg);
return arg;
}
方法:
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
// 第一次循环返回一个函数(...args) => a(b(...args),
// 第二次循环a即第一次循环返回的函数(...args) => 上面函数(b(...args))
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
console.log(compose(f1, f2, f3)("omg"));
三、Redux 上手
Redux是JavaScript应用的状态容器。它保证程序行为一致性且易于测试。
安装redux
npm install redux
redux上手
用户一个累加器举例
- 需要一个store来存储数据
- store里的reducer初始化state并定义state修改规则
- 通过dispatch一个action来提交对数据的修改
- action提交到reducer函数里,根据传入的action的type, 返回新的state
创建store,src/store/myIndex.js
import { createStore } from "redux";
function counter(state = 1, action) {
switch (action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
export default createStore(counter);
创建MyReduxPage
但是提交了action页面没有更新
修改订阅
此时ok
还可以在src/index.js的render里订阅状态变更
const render = () => {
ReactDom.render(<App />, document.querySelector("#root"));
};
render();
store.subscribe(render);
Redux拓展
核心实现
- 存储状态state
- 获取状态getState
- 更新状态dispatch
- 变更订阅subscribe
XRedux.js
export function createStore(reducer, enhancer) {
// 保存状态
let currentState = undefined;
// 订阅函数
let currentSubs = [];
// 获取状态
function getState() {
return currentState;
}
// 修改状态
function dispatch(action) {
currentState = reducer(currentState, action);
currentSubs.forEach((cb) => cb());
return action;
}
// 订阅
function subscribe(cb) {
currentSubs.push(cb);
}
// 确保state有默认值
dispatch({ type: "@@OOO/KKB-REDUX" });
return {
getState,
dispatch,
subscribe,
};
}
store从redux引入的createStore改成上面写的createStore
ok
异步
Redux只是个纯粹的状态管理器,默认只支持同步,实现异步任务;如延迟,网络请求,需要中间件的支持,如我们试用最简单的redux-thunk
和redux-logger
。
中间件就是一个函数,对store.dispatch方法进行改造, 在发出 Action 和执行Reducer 这两步之间,添加了其他功能。
npm install redux-thunk redux-logger --save
应用中间件,store/myIndex.js
切换redux库
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
// import { createStore } from "./XRedux";
function counter(state = 1, action) {
switch (action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
export default createStore(counter, applyMiddleware(logger, thunk));
添加按钮触发异步action
效果
中间件实现
核心任务是实现函数序列执行。 //把下面加入XRedux.js
// applyMiddleware(thunk, logger)(createStore)(reducer)
export function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args);
let dispatch = store.dispatch;
const middleApi = {
getState: store.getState,
dispatch,
};
// 给middleware参数,比如说dispatch
const middlewaresChain = middlewares.map((middleware) =>
middleware(middleApi)
);
/**
* // dispatch函数
* function dispatch(action) {
currentState = reducer(currentState, action);
currentSubs.forEach((cb) => cb());
return action;
}
// middleware(middleApi)执行后的样子
* (dispatch) => (action) => {
console.log(action.type + "执行了"); //sy-log
return dispatch(action);
}
(dispatch) => (action) => {
// action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
if (typeof action === "function") {
return action(dispatch, getState);
} else {
return dispatch(action);
}
}
执行:compose(...middlewaresChain)(dispatch)后
log:(action) => {
console.log(action.type + "执行了"); //sy-log
return dispatch(action);
}
(log) => (action) => {
// action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
if (typeof action === "function") {
return action(dispatch, getState);
} else {
return dispatch(action);
}
}
*/
dispatch = compose(...middlewaresChain)(dispatch);
return {
...store,
// 覆盖上面store里的dispatch
dispatch,
};
};
}
// 返回类似:(x) => f(g(x))
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
// return () => {};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
redux-logger/redux-thunk实现
使用自己写的中间件
function logger({ getState, dispatch }) {
return (dispatch) => (action) => {
console.log(action.type + "执行了"); //sy-log
return dispatch(action);
};
}
function thunk({ getState, dispatch }) {
return (dispatch) => (action) => {
// action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
if (typeof action === "function") {
return action(dispatch, getState);
} else {
return dispatch(action);
}
};
}
测试ok
1
1
1
1
1
1
1
1
1
1