一、redux
1.1 从一个计数器开始
- Counter.js
import React, { Component } from 'react'
import store from '../store'
window.__store = store
export default class Counter extends Component {
constructor () {
super()
this.state = {
num: store.getState().counter.num
}
}
componentDidMount () {
this.unsub = store.subscribe(() => {
this.setState({
num: store.getState().counter.num
})
})
}
componentWillUnmount () {
this.unsub()
}
render () {
store.getState().num = 15
return (<div>
<button onClick={() => store.dispatch({type: 'ADD', amount: 1})}>+</button>
<span>{this.state.num}</span>
<button onClick={() => store.dispatch({type: 'MINUS', amount: 1})}>-</button>
</div>)
}
}
- 我们用到的 redux 的功能:
- getState()
- createStore()
- combineReducers()
- dispatch()
- subscribe()
1.2 实现一个简单的 redux
function createStore (reducer) {
let state;
// 发布订阅:在数据更新后要执行的事件函数
let listeners = []
// 初始化 state: 因为 state 初始值是通过 reducer 的第一个参数的默认值,所以为了获取这个默认值,需要让 reducer 执行
dispatch({})
function getState() {
return JSON.parse(JSON.stringify(state))
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(item => item())
}
function subscribe(fn) {
listeners.push(fn)
return () => {
listeners = listeners.filter(item => item !== fn)
}
}
return {
dispatch,
getState,
subscribe
}
}
function combineReducers (reducers) {
// reducers {counter: counter, todo: todo}
// state -> {counter: {num: 1}, todo: {list: [], filter: 'all'}}
// counter 初始值 -> {num: 1}
// todo 初始值 -> {list: [], filter}
return (state = {}, action) => {
let obj = {}
for (let key in reducers) {
obj[key] = reducers[key](state[key], action)
}
return obj
}
}
export { createStore, combineReducers }
二、 react-redux
我们改写用 react-redux 改写 Counter;
2.1 首先通过 Provider 组件把 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'
ReactDOM.render(<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
2.2 改造 Counter
import React, { Component } from 'react'
import { connect } from '../react-redux'
class Counter extends Component {
render () {
// store.getState().num = 15
return (<div>
<button onClick={() => this.props.add(1)}>+</button>
<span>{this.props.num}</span>
<button onClick={() => this.props.minus(1)}>-</button>
</div>)
}
}
let actions = {
add (amount) {
return {
type: 'ADD',
amount
}
},
minus (amount) {
return {
type: 'MINUS',
amount
}
}
}
let mapDispatchToProps = (dispatch) => {
return {
add (amount) {
dispatch(actions.add(amount))
},
minus (amount) {
dispatch(actions.minus(amount))
}
}
}
export default connect(state => ({...state.counter}), actions)(Counter)
2.3 我们实现一个 react-redux
import React, { Component } from 'react'
// 使用 context 将 store 引入到组件树中,使用 context 首先创建 Context
let StoreContext = React.createContext(null)
class Provider extends Component {
render() {
return <StoreContext.Provider value={this.props.store}>
{
this.props.children
}
</StoreContext.Provider>
}
}
let bindActionCreators = (actions, dispatch) => {
let obj = {}
for (let key in actions) {
obj[key] = (...args) => dispatch(actions[key](...args))
}
return obj
}
// connect 是一个高阶组件
let connect = (mapStateToProps, mapDispatchToProps) => (Component) => {
return class HOCProxy extends React.Component {
// 使用 Context 访问组件树中通过 Context 共享的 store,需要给子组件定义一个 contextType 的静态属性
static contextType = StoreContext
constructor (props, context) {
super()
// 通过 mapStateToProps 把 store 初始化成当前组件的状态
// 为什么是私有状态?因为这是一个高阶组件,为了在 store 中数据更新时,我们可以订阅 store 中的状态变更然后更新 state 以期达到更新视图的目的
this.state = mapStateToProps(context.getState())
}
componentDidMount () {
// 虽然在子组件中我们省去了订阅 store 更新数据的操作,正是因为这个高阶组件中帮我们订阅了这些状态变更
this.unsub = this.context.subscribe(() => {
this.setState(mapStateToProps(this.context.getState()))
})
}
componentWillUnmount () {
// 当组件即将销毁时不要忘记取消订阅
this.unsub()
}
render () {
// 判断 mapDispatchToProps 到底是函数还是 actionCreator 对象,如果是 actionCreator 对象,则我们需要处理一下
// 如果是函数,我们直接执行 mapDispatchToProps 获取其返回结果
let dispatchToProps = {}
if (typeof mapDispatchToProps === 'object') {
dispatchToProps = bindActionCreators(mapDispatchToProps, this.context.dispatch)
} else {
dispatchToProps = mapDispatchToProps(this.context.dispatch)
}
// 最终返回组件,并且把从 store 中拿来并且放到 state 中的数据以 props 的形式传递给组件,修改 store 中状态也需要做相同的处理
return <Component {...this.state} {...dispatchToProps}/>
}
}
}
export { Provider, connect }