最近在研究react源码,顺便再研究一下 redux ,分享一下我的学习成果,让我们来实现一个简易的redux吧。
先给大家演示一下 redux 的最基础使用吧。
import React from 'react';
import { createStore } from 'redux';
import { connect, Provider } from 'react-redux';
const initState = {
couter: 0,
};
const reducer = (state = initState, action) => {
const { couter } = state;
switch (action.type) {
case 'INCREMENT':
return { couter: couter + 1 };
case 'DECREMENT':
return { couter: couter - 1 };
default:
return state;
}
};
const store = createStore(reducer);
const App = () => {
return (
<div>
<Provider store={store}>
<One />
</Provider>
</div>
);
};
const One = props => {
return (
<div>
<Tow />
</div>
);
};
const mapStateToProps = state => {
return {
...state,
};
};
const mapDispatchToProps = dispatch => {
return { dispatch };
};
const Tow = connect(
mapStateToProps,
mapDispatchToProps,
)(props => {
const { couter, dispatch } = props;
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
return (
<div>
<button onClick={increment}>{couter}</button>
</div>
);
});
export default App;
这份代码想必大家都已经很熟悉了,我们就从这开始揭开redux的神秘面纱。
Provider
定义一个上下文,这个简单,大致实现逻辑代码。
const ReactRedux = React.createContext(null);
const Provider = props => {
const { store } = props;
return (
<ReactRedux.Provider value={store}>{props.children}</ReactRedux.Provider>
);
};
connect
用于将全局状态数据注入到组件中,先实现其阉割版让大家理解一下。其背后的原理。(其中 store 大家为从 Provider 中获取的 store , 它拥有 dispatch 和 getState() 属性。)
const connect = (mapStateToProps, mapDispatchToProps) => Component => props => {
const store = useContext(ReactRedux);
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), props)
: {};
let dispathProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, props)
: {};
let allProps = { ...stateProps, ...dispathProps, ...props };
return <Component {...allProps} />;
};
从上面可以看出 connect 只是将state和dispatch通过组件传值的方式将数据传递进来。是不是很简单。
createStore
在实现 createStore 之前我们先将几个实例。 以下是一段普通的js代码
const initState = {
couter: 0,
color : 'red'
};
const renderApp = state => {
renderText(state.couter);
renderColor(state.color);
};
const renderText = (text)=>{
document.body.innerHTML = `<div>${text}</div>`;
}
const renderColor = (color)=>{
document.body.style.backgroundColor = color;
}
renderApp(initState);
initState.couter++;
initState.color = '#fff';
这个例子中我们可以看见,渲染之后即使我们改变数据也视图并为改变。于是我们对其进行改良。
const initState = {
couter: 0,
color: 'red',
};
const renderApp = state => {
renderText(state.couter);
renderColor(state.color);
};
const renderText = text => {
document.body.innerHTML = `<div>${text}</div>`;
};
const renderColor = color => {
document.body.style.backgroundColor = color;
};
const dispatch = action => {
switch (action.type) {
case 'couterinc':
initState.couter++;
renderApp(initState);
break;
case 'colorChang':
initState.color = action.color;
renderApp(initState);
break;
}
};
renderApp(initState);
dispatch({ type: 'couterinc' });
dispatch({ type: 'colorChang', color: '#fff' });
只需在每次改变数据之后进行重新渲染,让视图更新。这个例子中我们了解到,数据与视图直接其实就是个发布订阅的关系。于是我们将代码进行再次优化并真正实现createStore。
const createStore = reducer => {
let state;
const listeners = [];
const subscribe = listener => listeners.push(listener);
const getState = () => state;
const dispatch = action => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
dispatch({});
return { getState, dispatch, subscribe };
};
export default createStore;
其中 subscribe 将会在connect 中被调用, 用于重新渲染页面。 connect 完成版
const connect = (mapStateToProps, mapDispatchToProps) => Component => props => {
const [allProps, setAllProps] = useState({});
const store = useContext(ReactRedux);
useEffect(() => {
const update = () => {
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), props)
: {};
let dispathProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, props)
: {};
setAllProps({ ...stateProps, ...dispathProps, ...props });
};
update();
store.subscribe(() => update());
}, []);
return <Component {...allProps} />;
};
在这里他订阅了一个 update 方法用于更新视图。 到此为止我们就实现了一个简易版的 redux。