1、 组件的跨层级通信
React中使⽤用Context实现祖代组件向后代组件跨层级传值
(1) React.createContext
创建Context 对象。
(2) Context.Provider
Provider 接收 value 属性,传递给消费组件,允许消费组件订阅 context 的变化。一个 Provider可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使⽤,⾥层的会覆盖外层的数据。
当 Provider 的 value 值发⽣变化时,它内部的所有消费组件都会重新渲染。
provider变化会引起多个消费组件进行不必要的渲染,浪费性能,要避免。
示例
class App extends React.Component {
render() {
return (
);
}
}
将provider的value状态提升到父组件的state里面
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
);
}
}
(3) Class.contextType
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context
对象,可以在任何生命周期中访问到它,包括 render 函数中。用法存在局限性。
static contextType = ThemeContext;
[ClassName].contextType = ThemeContext;
(4) Context.Consumer
用于Class型组件订阅 context 变更。
(5) useContext
用于Function型组件订阅 context 变更。接收⼀个 context 对象( React.createContext 的返回值)并返回该 context 的当前值。当前的context 值由上层组件中距离当前组件最近的
// context.js 创建
import React from "react";
// 定义两个context传两个值
// provider和comsumer从context里获取
export const ThemeContext = React.createContext({themeColor: "pink"});
export const UserContext = React.createContext();
import React, {Component} from "react";
import ContextTypePage from "./ContextTypePage";
import {ThemeContext, UserContext} from "../Context";
import UseContextPage from "./UseContextPage";
import ConsumerPage from "./ConsumerPage";
export default class ContextPage extends Component {
constructor(props) {
super(props);
this.state = {
theme: {
themeColor: "red"
},
user: {
name: "xiaoming"
}
};
}
changeColor = () => {
const {themeColor} = this.state.theme;
this.setState({
theme: {
themeColor: themeColor === "red" ? "green" : "red"
}
});
};
render() {
const {theme, user} = this.state;
return (
ContextPage
。// context的嵌套
// contextType方法获取
// 可以消费多个context
);
}
}
pages/ContextTypePage.js
import React, {Component} from "react";
import {ThemeContext} from "../Context";
export default class ContextTypePage extends Component {
static contextType = ThemeContext; // 静态方法挂载在class下
render() {
const {themeColor} = this.context;
return (
ContextTypePage
);
}
}
pages/ConsumerPage.js
import React, {Component} from "react";
import {ThemeContext, UserContext} from "../Context";
// class组件中使用comsumer的方法
export default class ConsumerPage extends Component {
render() {
return (
// 接收context
{themeContext => (
<>
ConsumerPage
{userContext => } // 提取出组件
>
)}
);
}
}
// 防止嵌套太多可以将组件提出来, 接受context参数
function HandleUserContext(userCtx) {
return {userCtx.name};
}
pages/UseContextPage
import React, {useState, useEffect, useContext} from "react";
import {ThemeContext, UserContext} from "../Context";
// function组件使用context方法
export default function UseContextPage(props) {
const themeContext = useContext(ThemeContext); // 使用useContext钩子接受context
const {themeColor} = themeContext; // es6 方法提取出来
const userContext = useContext(UserContext);
return (
UseContextPage
// 在组件中直接使用
{userContext.name}
// 直接使用
);
}
2、高阶组件HOC
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数。
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
import React, {Component} from "react";
// hoc: 是⼀一个函数,接收⼀一个组件,返回另外⼀一个组件
//这⾥大写开头的Cmp是指function或者class组件
// 定义高阶组件
const foo = Cmp => props => {
return (
);
};
// 定义传入高阶组件的组件
function Child(props) {
return Child {props.name};
}
// 组装高阶组件
const Foo = foo(Child);
// 赋值和使用
export default class HocPage extends Component {
render() {
return (
HocPage
);
}
}
装饰器写法
利用ES7中出现的装饰器语法,首先安装和配置。
yarn add @babel/plugin-proposal-decorators
更新config-overrides.js
//配置完成后记得重启下
const { addDecoratorsLegacy } = require("customize-cra");
module.exports = override(
...,
addDecoratorsLegacy()//配置装饰器器
);
如果vscode对装饰器有warning,vscode设置里加上
javascript.implicitProjectConfig.experimentalDecorators": true
//HocPage.js
// !装饰器器只能⽤用在class上
// 执⾏行行顺序从下往上
@foo
@foo
class Child extends Component {
render() {
return Child {this.props.name};
}
}
// const Foo = foo2(foo(foo(Child)));
export default class HocPage extends Component {
render() {
return (
HocPage
{/* */}
);
}
}
注意事项:
不要在 render 方法中使用 HOC
render() {
// 每次调⽤用 render 函数都会创建⼀一个新的 EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 这将导致⼦子树每次渲染都会进⾏行行卸载,和重新挂载的操作!
return ;
}
- Redux原理
(1)reducer
reducer 就是⼀个纯函数,接收旧的 state 和 action,返回新的 state
;(previousState, action) => newState
称之为 reducer,是因为这种函数与被传⼊ Array.prototype.reduce(reducer, ?initialValue) ⾥的回调函数属于相同的类型
(2) reduce和聚合函数
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator
+ currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
将以下函数生成聚合函数,把第一个函数的返回值传给下一个函数。
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;
}
// 普通调用
const res = f1(f2(f3('omg')));
// output: f3 omg f2 omg f1 omg
// 使用聚合函数
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
console.log(compose(f1, f2, f3)("omg"));
(3) redux流程
- 需要⼀个store来存储数据
- store⾥的reducer初始化state并定义state修改规则
- 通过dispatch⼀个action来提交对数据的修改
- action提交到reducer函数⾥,根据传⼊的action的type,返回新的state
store存储数据,src/store/index.js
import {createStore} from "redux";
// 初始化 修改状态函数
function countReducer(state = 0, action) {
switch (action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
const store = createStore(countReducer); // 创建store
export default store;
创建ReduxPage
import React, {Component} from "react";
import store from "../store/";
export default class ReduxPage extends Component {
componentDidMount() {
store.subscribe(() => { // 变更订阅
this.forceUpdate();
});
}
add = () => {
store.dispatch({type: "ADD"}); // dispatch 更改数据,reducer里已经定义好了更改规则
};
minus = () => {
store.dispatch({type: "MINUS"});
};
render() {
return (
ReduxPage
{store.getState()}
// getState获取数据
);
}
}
(3) Redux拓展
实现:
存储状态state
获取状态getState
更新状态dispatch
变更订阅subscribe
// 实现createStore,返回getState,dispatch,subscribe函数
export default function createStore(reducer, enhancer) {
// ehancer为applyMiddleware返回的结果
if (enhancer) {
return enhancer(createStore)(reducer);
}
// 函数内存储state的值
let currentState;
// 函数内存储监听数组
let currentListeners = [];
function getState() {
return currentState; // 返回当前的值
}
function dispatch(action) {
currentState = reducer(currentState, action); // 用reducer规则更新currentState
currentListeners.forEach(listener => listener());
//执行每一个listener,告诉subscribe的地方更新currentState(指的是页面重新渲染,展示新的值)
return action;
}
function subscribe(listener) {
currentListeners.push(listener); // 传入一个listener,加入到监听数组
return () => {
currentListeners = []; // 返回一个销毁数组的方法
};
}
dispatch({type: "KKBREDUX/OOOO"}); // 执行reducer中的default
return {
getState,
dispatch,
subscribe
};
}
(5) Redux中间件
Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务 ⽐如延
迟,⽹络请求,需要中间件的⽀持,⽐如我们使⽤最简单的redux-thunk和
redux-logger 。
应用中间价 store.js
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counterReducer from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(thunk, logger));
asyAdd = () => {
// 实现一个中间件使dispatch能够接收函数
store.dispatch((dispatch, getState) => {
setTimeout(() => {
// console.log("now ", getState()); //sy-log
dispatch({type: "ADD", payload: 1});
}, 1000);
});
};
实现 applyMiddleware
export default function applyMiddleware(...middlewares) {
return createStore => reducer => {
// 从 createStore(reducer)里拿到dispatch,getState
const store = createStore(reducer);
let dispatch = store.dispatch;
// 柯里化 先传入参数,闭包保存
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action,
...args)
};
// 先传入参数执行
const middlewareChain = middlewares.map(middleware =>
middleware(midApi));
// 聚合函数加强dispatch
dispatch = compose(...middlewareChain)(store.dispatch);
return {
...store,
// 加强版的dispatch
dispatch
};
};
}
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) =>
a(b(...args)));
}
实现redux-logger
function logger({getState}) {
// action 在调用加强后dispach时传进来的 dispatch({type:'ADD'})
// next 表示聚合函数 f1( f2(...args) ) 中的 返回值,会被外层调用
return next => action => {
console.log("====================================");
console.log(action.type + "执⾏了!"); //sy-log
const prevState = getState();
console.log("prev state", prevState); //sy-log
const returnValue = next(action);
const nextState = getState();
console.log("next state", nextState); //sy-log
console.log("====================================");
return returnValue;
};
}
实现 redux-thunk
function thunk({dispatch, getState}) {
return next => action => {
// 增加了处理函数的能力
if (typeof action === "function") {
return action(dispatch, getState);
}
return next(action);
};
}
实现redux-promis
function promise({dispatch}) {
return next => action => {
return isPromise(action) ? action.then(dispatch) :
next(action);
};
}
- react-redux
(1) react-hooks
useReducer
useState
的替代方案。它接收一个形如 (state, action) => newState
的 reducer,并返回当前的 state 以及与其配套的 dispatch
方法。
在某些场景下,useReducer
会比 useState
更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer
还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch
而不是回调函数 。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
>
);
}
useCallback
// 返回一个依赖于参数a,b执行的回调函数 memoizedCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
(2) 使用react-redux
在redux的基础上添加了两个api
- Provider 为后代组件提供store , 替代getState方法获取数据,使用 useContext
- connect 为组件提供数据和变更方法,替代subscribe方法,执行绑定数据和更新组件功能
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
参数
- mapStateToProps(state, [ownProps]): stateProps ] (Function)
该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果定义该参数,组件将会监听 Redux store 的变化,否则 不监听。
ownProps 是当前组件自身的props,如果指定了,那么只要组件接收到新的 props,mapStateToProps 就会被调用,mapStateToProps 都会被重新计算, mapDispatchToProps 也
会被调用。注意性能! - mapDispatchToProps(dispatch, [ownProps]): dispatchProps ] (Object or Function):
如果省略这个 mapDispatchToProps 参数,dispatch 会注入到你的组件 props
中。可传递对象和函数,见示例。
全剧提供store index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
// import {Provider} from "react-redux";
import {Provider} from "./kReactRedux";
import store from "./store/";
// 把Provider放在根组件外层,使子组件能获得store
ReactDOM.render(
,
document.getElementById("root")
);
import React, {Component} from "react";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
// import {bindActionCreators, connect} from "../kReactRedux";
// connect用于连接React组件与store, 返回一个新的已经与store连接的组件类(HOC)
export default connect(
// 第一个参数 mapStateToProps, state 从 store中获取值
(state, ownProps) => {
return {
count: state.count
};
},
// 第二个参数mapDispatchToProps,可以是 Object / Fucntion
// Object形式
{
add: () => ({type: "ADD"}),
minus: () => ({type: "MINUS"})
}
// Fucntion形式,参数是dispatch与ownProps
// 使用 bindActionCreators 函数, 作用是绑定dispatch和creators
(dispatch, ownProps) => {
let creators = {
add: payload => ({type: "ADD", payload}),
minus: () => ({type: "MINUS"})
};
creators = bindActionCreators(creators, dispatch);
return {dispatch, ...creators};
}
// 不使用
(dispatch, ownProps) => {
return {
add: payload => dispatch({type: "ADD", payload}),
minus: () => dispatch({type: "MINUS"})
}
};
}
)(
class ReactReduxPage extends Component {
add = () => {
this.props.dispatch({type: "ADD"});
};
render() {
const {count, dispatch, add, minus} = this.props;
return (
ReactReduxPage
omg:{count}
);
}
}
);
实现react-redux
import React, {useContext, useReducer, useLayoutEffect} from "react";
const Context = React.createContext();
export const connect = (
mapStateToProps = state => state,
mapDispatchToProps
) => WrappendComponent => props => {
// 定义全局context存储store
const store = useContext(Context);
const {dispatch, getState, subscribe} = store;
// 得到stateProps对象, 直接传入组件
const stateProps = mapStateToProps(getState());
let dispatchProps = {dispatch};
// hooks实现forceupdate
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
if (typeof mapDispatchToProps === "function") {
dispatchProps = mapDispatchToProps(dispatch);
} else if (typeof mapDispatchToProps === "object") {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
}
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
forceUpdate();
});
return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [store]);
return ;
};
export function Provider({store, children}) {
return {children} ;
}
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args));
}
function bindActionCreators(creators, dispatch) {
const obj = {};
for (let key in creators) {
obj[key] = bindActionCreator(creators[key], dispatch);
}
return obj;
}
(3) react-redux hooks API及实现
useSelector 获取store state
useDispatch 获取dispatch
import React, {useCallback} from "react";
import {useSelector, useDispatch} from "react-redux";
export default function ReactReduxHookPage({value}) {
const dispatch = useDispatch();
const add = useCallback(() => {
dispatch({type: "ADD"});
}, []);
const count = useSelector(({count}) => count);
return (
ReactReduxHookPage
{count}
);
}
实现
export function useSelector(selector) {
const store = useStore();
const {getState, subscribe} = store;
const selectedState = selector(getState());
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
forceUpdate();
});
return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [store]);
return selectedState;
}
export function useDispatch() {
const store = useStore();
return store.dispatch;
}
export function useStore() {
const store = useContext(Context);
return store;
}
- React-router