一、redux

1.1 从一个计数器开始

  • Counter.js
  1. import React, { Component } from 'react'
  2. import store from '../store'
  3. window.__store = store
  4. export default class Counter extends Component {
  5. constructor () {
  6. super()
  7. this.state = {
  8. num: store.getState().counter.num
  9. }
  10. }
  11. componentDidMount () {
  12. this.unsub = store.subscribe(() => {
  13. this.setState({
  14. num: store.getState().counter.num
  15. })
  16. })
  17. }
  18. componentWillUnmount () {
  19. this.unsub()
  20. }
  21. render () {
  22. store.getState().num = 15
  23. return (<div>
  24. <button onClick={() => store.dispatch({type: 'ADD', amount: 1})}>+</button>
  25. <span>{this.state.num}</span>
  26. <button onClick={() => store.dispatch({type: 'MINUS', amount: 1})}>-</button>
  27. </div>)
  28. }
  29. }
  • 我们用到的 redux 的功能:
  1. getState()
  2. createStore()
  3. combineReducers()
  4. dispatch()
  5. subscribe()

1.2 实现一个简单的 redux

  1. function createStore (reducer) {
  2. let state;
  3. // 发布订阅:在数据更新后要执行的事件函数
  4. let listeners = []
  5. // 初始化 state: 因为 state 初始值是通过 reducer 的第一个参数的默认值,所以为了获取这个默认值,需要让 reducer 执行
  6. dispatch({})
  7. function getState() {
  8. return JSON.parse(JSON.stringify(state))
  9. }
  10. function dispatch(action) {
  11. state = reducer(state, action)
  12. listeners.forEach(item => item())
  13. }
  14. function subscribe(fn) {
  15. listeners.push(fn)
  16. return () => {
  17. listeners = listeners.filter(item => item !== fn)
  18. }
  19. }
  20. return {
  21. dispatch,
  22. getState,
  23. subscribe
  24. }
  25. }
  26. function combineReducers (reducers) {
  27. // reducers {counter: counter, todo: todo}
  28. // state -> {counter: {num: 1}, todo: {list: [], filter: 'all'}}
  29. // counter 初始值 -> {num: 1}
  30. // todo 初始值 -> {list: [], filter}
  31. return (state = {}, action) => {
  32. let obj = {}
  33. for (let key in reducers) {
  34. obj[key] = reducers[key](state[key], action)
  35. }
  36. return obj
  37. }
  38. }
  39. export { createStore, combineReducers }

二、 react-redux

我们改写用 react-redux 改写 Counter;

2.1 首先通过 Provider 组件把 store 引入到组件树中;

  • index.js
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import './index.css';
  4. import App from './App'
  5. import { Provider } from './react-redux'
  6. import store from './store'
  7. ReactDOM.render(<Provider store={store}>
  8. <App />
  9. </Provider>, document.getElementById('root'));

2.2 改造 Counter

  1. import React, { Component } from 'react'
  2. import { connect } from '../react-redux'
  3. class Counter extends Component {
  4. render () {
  5. // store.getState().num = 15
  6. return (<div>
  7. <button onClick={() => this.props.add(1)}>+</button>
  8. <span>{this.props.num}</span>
  9. <button onClick={() => this.props.minus(1)}>-</button>
  10. </div>)
  11. }
  12. }
  13. let actions = {
  14. add (amount) {
  15. return {
  16. type: 'ADD',
  17. amount
  18. }
  19. },
  20. minus (amount) {
  21. return {
  22. type: 'MINUS',
  23. amount
  24. }
  25. }
  26. }
  27. let mapDispatchToProps = (dispatch) => {
  28. return {
  29. add (amount) {
  30. dispatch(actions.add(amount))
  31. },
  32. minus (amount) {
  33. dispatch(actions.minus(amount))
  34. }
  35. }
  36. }
  37. export default connect(state => ({...state.counter}), actions)(Counter)

2.3 我们实现一个 react-redux

  1. import React, { Component } from 'react'
  2. // 使用 context 将 store 引入到组件树中,使用 context 首先创建 Context
  3. let StoreContext = React.createContext(null)
  4. class Provider extends Component {
  5. render() {
  6. return <StoreContext.Provider value={this.props.store}>
  7. {
  8. this.props.children
  9. }
  10. </StoreContext.Provider>
  11. }
  12. }
  13. let bindActionCreators = (actions, dispatch) => {
  14. let obj = {}
  15. for (let key in actions) {
  16. obj[key] = (...args) => dispatch(actions[key](...args))
  17. }
  18. return obj
  19. }
  20. // connect 是一个高阶组件
  21. let connect = (mapStateToProps, mapDispatchToProps) => (Component) => {
  22. return class HOCProxy extends React.Component {
  23. // 使用 Context 访问组件树中通过 Context 共享的 store,需要给子组件定义一个 contextType 的静态属性
  24. static contextType = StoreContext
  25. constructor (props, context) {
  26. super()
  27. // 通过 mapStateToProps 把 store 初始化成当前组件的状态
  28. // 为什么是私有状态?因为这是一个高阶组件,为了在 store 中数据更新时,我们可以订阅 store 中的状态变更然后更新 state 以期达到更新视图的目的
  29. this.state = mapStateToProps(context.getState())
  30. }
  31. componentDidMount () {
  32. // 虽然在子组件中我们省去了订阅 store 更新数据的操作,正是因为这个高阶组件中帮我们订阅了这些状态变更
  33. this.unsub = this.context.subscribe(() => {
  34. this.setState(mapStateToProps(this.context.getState()))
  35. })
  36. }
  37. componentWillUnmount () {
  38. // 当组件即将销毁时不要忘记取消订阅
  39. this.unsub()
  40. }
  41. render () {
  42. // 判断 mapDispatchToProps 到底是函数还是 actionCreator 对象,如果是 actionCreator 对象,则我们需要处理一下
  43. // 如果是函数,我们直接执行 mapDispatchToProps 获取其返回结果
  44. let dispatchToProps = {}
  45. if (typeof mapDispatchToProps === 'object') {
  46. dispatchToProps = bindActionCreators(mapDispatchToProps, this.context.dispatch)
  47. } else {
  48. dispatchToProps = mapDispatchToProps(this.context.dispatch)
  49. }
  50. // 最终返回组件,并且把从 store 中拿来并且放到 state 中的数据以 props 的形式传递给组件,修改 store 中状态也需要做相同的处理
  51. return <Component {...this.state} {...dispatchToProps}/>
  52. }
  53. }
  54. }
  55. export { Provider, connect }