实现一个极简版本的redux
y总在5.4节上通过redux演示的一些操作基本都可以正确实现,更复杂一些的没试(hh肯定是有bug的)。
只是用来辅助理解redux的原理。
已知问题:类似于下面这样的组合reducer
需要给state
传入一个空对象作为默认值
const f3 = (state = {}, action) => {
return {
f1: f1(state.f1, action),
f2: f2(state.f2, action),
};
};
代码实现
比较核心代码都放在了redux.js
中。
redux.js
import React, { PureComponent, createContext, forwardRef } from "react";
const NOOP = () => {};
const isFunction = func => typeof func === "function";
const isPlainObject = obj => Object.prototype.toString.call(obj) === "[object Object]";
// redux可以跨越层级传递状态是利用了React中的Context
// 这里创建一个Context用来实现Provider和connect
const StoreContext = createContext({
state: {},
dispatch: NOOP,
});
StoreContext.displayName = "StoreContext";
// 用来存储全局store
class Store {
state = null;
// 不是数组也可以
reducers = [];
subsQueue = [];
constructor(reducer, state) {
this.reducers.push(reducer);
// 对state做一个初始化的处理
this.state = reducer(state, { type: "REDUX_INIT" });
}
getState = () => this.state;
subscribe = (callback) => {
this.subsQueue.push(callback);
};
dispatch = (action) => {
let newState = this.state;
this.reducers.forEach(cb => {
newState = cb(newState, action);
});
if (newState === this.state) return;
this.state = newState;
this.subsQueue.forEach(cb => cb())
};
}
export default function configureStore(config) {
const { reducer } = config;
return new Store(reducer);
}
export const combineReducers = (reducers) => (state = {}, action) => {
let newState = state;
Object.entries(reducers).forEach(([key, cb]) => {
newState = {
...newState,
[key]: cb(state[key], action)
};
});
return newState;
};
export class Provider extends PureComponent {
constructor(props) {
super(props);
this.state = {
state: props.store.getState(),
dispatch: props.store.dispatch,
};
}
componentDidMount() {
const { getState, subscribe } = this.props.store;
// 当store发生变化后,将最新的值传递下去
subscribe(() => {
this.setState({
state: getState()
});
});
}
render() {
return (
<StoreContext.Provider value={this.state}>
{this.props.children}
</StoreContext.Provider>
);
}
}
// connect利用高阶函数和Context获取store
export const connect = (mapStateToProps = NOOP, mapDispatchToProps = NOOP) => (WrappedComponent) => {
const Connected = class extends PureComponent {
static contextType = StoreContext;
render() {
let { state, dispatch } = this.context;
let mappedState = mapStateToProps(state);
let mappedDispatch;
if (isPlainObject(mapDispatchToProps)) {
mappedDispatch = Object.entries(mapDispatchToProps).reduce((acc, [key, cb]) => {
acc[key] = (...args) => {
const action = cb(...args);
dispatch(action);
};
return acc;
}, {});
} else {
mappedDispatch = mapDispatchToProps(dispatch);
}
let { innerRef, ...props } = this.props;
return (
<WrappedComponent ref={innerRef} {...props} {...mappedState} {...mappedDispatch} />
);
}
};
// 用来处理ref转发(不是必须的)
const Forward = forwardRef((props, ref) => {
return <Connected innerRef={ref} {...props} />;
});
Forward.displayName = "Forward";
return Forward;
};
使用方式
将configureStore
,combineReducers
,Provider
和connect
替换成redux.js
中所导出的。
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import configureStore, { Provider, combineReducers } from "./redux";
const f1 = (state = 0, action) => {
switch(action.type) {
case "add":
return state + action.value;
case "sub":
return state - action.value;
default:
return state;
}
};
const f2 = (state = ":", action) => {
switch(action.type) {
case "concat":
return state + action.character;
default:
return state;
}
};
const f3 = combineReducers({
number: f1,
string: f2,
});
const store = configureStore({
reducer: f3
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
App.js
import React, { Component } from 'react';
import Number from './Number';
import String from './String';
class App extends Component {
state = { }
render() {
return (
<React.Fragment>
<Number />
<hr />
<String />
</React.Fragment>
);
}
}
export default App;
Number.js
import React, {Component} from 'react';
import {connect} from "./redux";
class Number extends Component {
state = {}
handleClick = () => {
this.props.concat('y');
}
render() {
return (
<React.Fragment>
<h3>Number:</h3>
<div>{this.props.number}</div>
<button onClick={this.handleClick}>添加</button>
</React.Fragment>
);
}
}
const mapStateToProps = (state, props) => {
return {
number: state.number,
}
};
const mapDispatchToProps = {
concat: (c) => {
return {
type: "concat",
character: c,
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Number);
orz