redux是一种数据流管理方式,它将数据统一存储到store中,数据的修改需要使用dispatch()
方法,同时需要配置reduce用来接受action,action表示要进行的修改,reduce接收到action后会修改store并返回一个全新的store。
我们在使用redux时需要搭配react-redux。react-redux主要作用是协助组件订阅store,通过connect()
方法来实现组件对store的订阅。
class List extends React.Component{}
export default connect(mapStateToProps, mapDispatchToProps)(List)
import List from './List';
import {createStore} from 'redux';
const store = createStore(reducer);
<Provider store={store}>
<List />
</Provider>
我们要做的就是实现Provider
组件以及connect
方法。
功能点:
- 接收props中的store,并将其放入上下文,供子组件来使用。
实现起来比较简单:
先创建一个统一的上下文:
Context.js:
import {createContext} from 'react';
const Context = createContext(null);
export default Context;
Provider.js:
import React, {createContext} from 'react';
const Context = createContext(null);
class Provider extends React.Component{
render(){
return (
<Context.Provider value={store: this.props.store}>
{this.props.children}
</Context.Provider>
)
}
}
功能点:
- 将组件订阅到store上
- 根据mapStateToProps将store的数据传给组件
- store发生变化时组件能随之更新
我们只实现一个简单版的,只考虑mapStateToProps。
import React, {useContext, useReducer, useEffect} from 'react';
import Context from './Context.js';
function connect(mapStateToProps){
function selector(mapStateToProps, store){ // 计算属性
return {...mapStateToProps(store.getState()), dispatch: store.dispatch};
}
function reducer(state, action){
return {updateCount: ++state.updateCount};
}
return (WrapperComponent)=>{
function ConnectComponent(){
const {store} = useContext(Context);
// 使用useReducer替代useState
const [, dispatch] = useReducer(reducer, {updateCount: 0});
const props = selector(mapStateToProps, store);
useEffect(()=>{
store.subscribe(() => {
dispatch({type:'update'});
})
}, [])
return <WrapperComponent {...props} />
}
return ConnectComponent;
}
}
以上就实现了一个非常非常简单的react-redux。
简单的就说明有很多问题没有考虑到,比如性能问题。
如果props没变,最好是不让页面re-render,但现在我们的代码没有这样的功能,我们来实现一下。
useEffect(()=>{
store.subscribe(()=> {
if(/*props发生了改变*/){
dispatch({type: 'update'})
}
})
}, [])
我们使用浅比较来对props进行比较(深比较消耗较高,而且props层级不好确定)。
shallowEqual.js
// Object.is()方法的pollyfill,对基本数据类型能进行准确比较
function is(x, y){
if(x === y){
return x !== 0 || y !== 0;
} else {
return x !== x || y !== y;
}
}
function shallowEqual(objA, objB){
if(is(objA, objB)){
return true;
}
if(typeof objA !== 'object' || typeof objB !== 'object'
|| objA === null || objB === null){
return false;
}
let keysA = Object.keys(objA);
for(let i = 0; i < keysA.length; i++){
if(!objB.hasOwnProperty(key)){
return false;
} else if(!is(objA[keys[i]], objB[keys[i]])){
return false
}
}
}
在connect方法中使用shallowEqual比较props是否发生变化:
修改ConnectComponent方法
function ConnectComponent(){
const {store} = useContext(Context);
// 使用useReducer替代useState
const [, dispatch] = useReducer(reducer, {updateCount: 0});
const props = selector(mapStateToProps, store);
const renderProps = useRef(null); // 用来保存props
useEffect(()=> {
const props = selector(mapStateToProps, store);
if(!shallowEqual(renderProps.current, props)){
renderProps.current = props;
dispatch({type: 'update'})
}
store.subscribe(() => {
const props = selector(mapStateToProps, store);
if(!shallowEqual(renderProps.current, props)){
renderProps.current = props;
dispatch({type: 'update'})
}
})
}, [])
return <WrapperComponent {...renderProps.current} />
}
再来更改下selector
方法,确保state不变时,返回的props的引用也不变。
function ConnectComponent(){
const {store} = useContext(Context);
// 使用useReducer替代useState
const [, dispatch] = useReducer(reducer, {updateCount: 0});
function selectorFactory(mapStateToProps){
let lastProps = null;
let firstCall = true;
function handleFirstCall(state){
firstCall = false;
return {...mapStateToProps(state), dispatch: store.dispatch}
}
function handleSubsequentCall(state){
const newProps = mapStateToProps(state);
if(!shallowEqual(lastProps, newProps)){
lastProps = newProps;
return {...newProps, dispatch: store.dispatch}
}
return {...lastProps, dispatch: store,dispatch}
}
function finalCall(state){
if(firstCall){
return handleFirstCall(state)
} else {
return handleSubsequentCall(state);
}
}
return (state) => finalCall(state);
}
const renderProps = useRef(null); // 用来保存props
useEffect(()=> {
const selector = selectorFactory(mapStateToProps);
const props = selector(store.getState())
if(!shallowEqual(renderProps.current, props)){
renderProps.current = props;
dispatch({type: 'update'})
}
store.subscribe(() => {
const selector = selectorFactory(mapStateToProps);
const props = selector(store.getState())
if(!shallowEqual(renderProps.current, props)){
renderProps.current = props;
dispatch({type: 'update'})
}
})
}, [])
return <WrapperComponent {...renderProps.current} />
}