先学习三个Hooks API
- useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init)
useState的替代方案,它接收一个形如(state, action) => newState的reducer,并返回当前的state,以及与之匹配的dispatch方法
- useEffect
赋值给 useEffect 的函数会在组件渲染到屏幕之后延迟执行。默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。
- useLayoutEffect
其函数签名与useEffect相同,但它会在所有的DOM变更之后同步调用 effect。可以使用它来读取DOM布局并同步触发重渲染。在浏览器执行绘制之前,内部的更新计划将被同步刷新。
import React, {useReducer, useLayoutEffect, useEffect} from "react";
import {counterReducer} from "../store";
const init = initArg => {
//改变传入的初始值,这里是把string转化为int
return initArg - 0;
};
export default function HooksPage(props) {
const [state, dispatch] = useReducer(counterReducer, "0", init);
useEffect(() => {
console.log("useEffect"); //sy-log
});
useLayoutEffect(() => {
console.log("useLayoutEffect"); //sy-log
});
console.log("---"); //sy-log
return (
<div>
<h3>HooksPage</h3>
<p>{state}</p>
<button onClick={() => dispatch({type: "ADD"})}>add</button>
</div>
); }
使用react-redux
yarn add react-redux
提供了2个Api:(provider,connect)
connect
为组件提供数据和变更方法(连接React组件与Redux store。返回一个新的已与 Redux store 连接的组件类)
mapStateToProps(state,ownProps)
- 该回调函数必须返回一个纯对象,这个对象会和组件的props合并,如果定义该参数,组件将会监听 Redux store的变化,否则不监听。
- ownProps是当前组件的props,如果指定了,那么组件只要接收新的peops,mapStateToProps就会被调用,,mapStateToProps都会被重新计算,mapDispatchToProps也会被调用,所以为了避免一些性能问题,,我们通常不传ownProps参数。
mapDispatchToProps(dispatch,ownProps),
- 如果省略这个参数,默认情况下,dispatch会注入到props中。
- 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所 定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中 方法会将action creator的返回值作为参数执行。这些属性会被合并到组件的 props 中。
- 如果传递的是一个函数,该函数将接收一个 函数,然后由你来决定如何返回一个对象。 4.ownProps是当前组件自身的props,如果指定了,那么只要组件接收到新的 props, mapDispatchToProps 就会被调用。注意性能!
mergeProps(stateProps, dispatchProps, ownProps)
- 如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身 的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件 中。
connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])
步骤一: provider为后代组件提供store,全局提供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 store from "./store/";
// 把Provider放在根组件外层,使子组件能获得store
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
步骤二: 获取状态数据---ReactReduxPage.js
import React, { Component } from "react";
import { connect } from "react-redux";
class ReactReduxPage extends Component {
render() {
const { num, add, minus, asyAdd } = this.props;
return (
<div>
<h1>ReactReduxPage</h1>
<p>{num}</p>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
); }
}
const mapStateToProps = state => {
return {
num: state,
};
};
const mapDispatchToProps = {
add: () => {
return { type: "add" };
},
minus: () => {
return { type: "minus" };
}
};
export default connect(
mapStateToProps, //状态映射 mapStateToProps
mapDispatchToProps, //派发事件映射
)(ReactReduxPage);
步骤三: 详情使用
// 注意 这里使用的是export default connect()()格式,通常在项目里会做拆分使用export default connect(mapStateToProps,mapDispatchToProps)(Component)
import React, {Component} from "react";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
// connect用于连接React组件与store, 返回一个新的已经与store连接的组件类(HOC)
export default connect(
// mapStateToProps Fucntion
// !慎重定义ownProps,因为你一旦定义ownProps,那么每当ownProps发生改变的时候,当前的 mapStateToProps都会被调用,
// !这里的state也会被重新计算,容易影响性能 state => {
// console.log("mapStateToProps"); //sy-log
return {
count: state
};
},
// mapDispatchToProps 接收Object||Fucntion
// Object 此时props中没有dispacth,但是有action creators,内部实现dispatch //
// {
// add: () => ({type: "ADD"}),
// minus: () => ({type: "MINUS"})
// }
// Fucntion 参数是dispatch与ownProps
// !慎重定义ownProps,因为你一旦定义ownProps,那么每当ownProps发生改变的时候,当前的
mapStateToProps都会被调用,容易影响性能
(dispatch, ownProps) => {
console.log("mapDispatchToProps--", ownProps); //sy-log
let creators = {
add: payload => ({type: "ADD", payload}),
minus: () => ({type: "MINUS"})
};
creators = bindActionCreators(creators, dispatch);
return {dispatch, ...creators};
}
)(
class ReactReduxPage extends Component {
add = () => {
this.props.dispatch({type: "ADD"});
};
render() {
console.log("props", this.props); //sy-log
const {count, dispatch, add, minus} = this.props;
return (
<div>
<h3>ReactReduxPage</h3>
<p>omg:{count}</p>
<button onClick={this.add}>add-use dispatch</button>
<button onClick={() => add(100)}> add</button>
<button onClick={minus}>minus</button>
</div>
);
}
});
实现react-redux
高阶组件实现connect方法
import React, {useContext, useEffect, useReducer, useLayoutEffect} from "react";
// Provider在index.js,把store传递下来,用到了context
// 使得所有的子组件都有机会接收到store
const Context = React.createContext();
// 实现:connect(({count}) => ({count}), { add: ()=>({type: 'ADD})})(Cmp)
export const connect = (
mapStateToProps = state => state,
mapDispatchToProps
) => WrappedComponent => props => {
// 读取store state
const store = useContext(Context);
const {getState, dispatch, subscribe} = store;
const stateProps = mapStateToProps(getState());
// dispatch object | function
let dispatchProps = {dispatch};
if (typeof mapDispatchToProps === "function") {
dispatchProps = mapDispatchToProps(dispatch);
} else if (typeof mapDispatchToProps === "object") {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
}
// 这里实现了函数组件版本的forceUpdate,可去参考官网
const [forceUpdate] = useReducer(x => x + 1, 0);
// 这里用到了前文中说的useLayoutEffect,为了实时的更新订阅,而不是延迟,return实现订阅之后要取消订阅,[store]关联数据
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
// store state 发生改变 forceUpdate是强制更新
forceUpdate();
});
return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [store]);
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />;
};
// 提供者 提供store 因为store当中有state dispatch subscribe
export function Provider({store, children}) {
return <Context.Provider value={store}>{children}</Context.Provider>;
}
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args));
}
export function bindActionCreators(creators, dispatch) {
const obj = {};
for (let key in creators) {
obj[key] = bindActionCreator(creators[key], dispatch);
}
return obj;
}
react-redux hooks API及实现
- useSelector获取数据
- useDispatch获取dispatch
export function useSelector(selector) {
const store = useStore();
const {getState, subscribe} = store;
const selectedState = selector(getState());
// 手动实现一个forceUpdate
const [forceUpdate] = useReducer(x => x + 1, 0);
// 这里用到了前面解释hook时候的useLayoutEffect,为了实时,而不是延迟更新
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
// store state 发生改变 forceUpdate是强制更新
forceUpdate();
});
return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [store]);
return selectedState;
}
export function useDispatch() {
const store = useStore();
return store.dispatch;
}
function useStore() {
const store = useContext(Context);
return store;
}
react-router自定义hook使用
import React, {useCallback} from "react";
import {useSelector, useDispatch} from "../ReactRedux";
export default function ReduxHooksPage(props) {
// 获取状态值
const count = useSelector(({count}) => count);
// 获取dispatch
const dispatch = useDispatch();
// [] 作为 useCallback 的依赖列表。这确保了callback不会在再次渲染时改变,因此 React不会在非必要的时候调用它。
// useMemo对参数缓存,useCallback对函数缓存,只有依赖项变更的时候执行或者改变
// 用useCallback包裹以避免随渲染发生改变
const add = useCallback(() => {
dispatch({type: "ADD"});
}, []);
return (
<div>
<h3>ReduxHooksPage</h3>
<p>{count}</p>
<button onClick={add}>add</button>
</div>
);
}