zoukankan      html  css  js  c++  java
  • 手写redux

     <body>
        <div id='title'></div>
        <div id='content'></div>
      </body>
    
    const appState = {
      title: {
        text: 'React.js 小书',
        color: 'red',
      },
      content: {
        text: 'React.js 小书内容',
        color: 'blue'
      }
    }
    
    function renderApp (appState) {
      renderTitle(appState.title)
      renderContent(appState.content)
    }
    
    function renderTitle (title) {
      const titleDOM = document.getElementById('title')
      titleDOM.innerHTML = title.text
      titleDOM.style.color = title.color
    }
    
    function renderContent (content) {
      const contentDOM = document.getElementById('content')
      contentDOM.innerHTML = content.text
      contentDOM.style.color = content.color
    }
    
    很简单,renderApp 会调用 rendeTitle 和 renderContent,而这两者会把 appState 里面的数据通过原始的 DOM 操作更新到页面上,调用:
    
    renderApp(appState)

     问题:所有模块都可以没有限制的对appState进行修改,全局变量,出现问题的时候 debug 起来就非常困难,这就是老生常谈的尽量避免全局变量。

    但不同的模块(组件)之间确实需要共享数据,这些模块(组件)还可能需要修改这些共享数据,就像上一节的“主题色”状态(themeColor)。这里的矛盾就是:“模块(组件)之间需要共享数据”,和“数据可能被任意修改导致不可预料的结果”之间的矛盾。

    我们可以学习 React.js 团队的做法,把事情搞复杂一些,提高数据修改的门槛:模块(组件)之间可以共享数据,也可以改数据。但是我们约定,这个数据并不能直接改,你只能执行某些我允许的某些修改,而且你修改的必须大张旗鼓地告诉我。

    于是我们定义一个函数,叫 dispatch,它专门负责数据的修改:

    function dispatch (action) {
      switch (action.type) {
        case 'UPDATE_TITLE_TEXT':
          appState.title.text = action.text
          break
        case 'UPDATE_TITLE_COLOR':
          appState.title.color = action.color
          break
        default:
          break
      }
    }

    通过action的type进行识别修改

    dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
    dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色

    抽离store和监听数据的变化

    function createStore (state, stateChanger) {
      const getState = () => state
      const dispatch = (action) => stateChanger(state, action)
      return { getState, dispatch }
    }
    
    createStore 接受两个参数,一个是表示应用程序状态的 state;另外一个是 stateChanger,它来描述应用程序状态会根据 action 发生什么变化,其实就是相当于本节开头的 dispatch 代码里面的内容。
    
    createStore 会返回一个对象,这个对象包含两个方法 getState 和 dispatch。getState 用于获取 state 数据,其实就是简单地把 state 参数返回。
    
    dispatch 用于修改数据,和以前一样会接受 action,然后它会把 state 和 action 一并传给 stateChanger,那么 stateChanger 就可以根据 action 来修改 state 了。

    添加监听

    function createStore (state, stateChanger) {
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)
      const getState = () => state
      const dispatch = (action) => {
        stateChanger(state, action)
        listeners.forEach((listener) => listener())
      }
      return { getState, dispatch, subscribe }
    }

     reducer 纯函数 

    function createStore (reducer) {
      let state = null
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)
      const getState = () => state
      const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
      }
      dispatch({}) // 初始化 state
      return { getState, dispatch, subscribe }
    }

    完整的

    function createStore (state, stateChanger) {
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)
      const getState = () => state
      const dispatch = (action) => {
        stateChanger(state, action)
        listeners.forEach((listener) => listener())
      }
      return { getState, dispatch, subscribe }
    }
    
    function renderApp (appState) {
      renderTitle(appState.title)
      renderContent(appState.content)
    }
    
    function renderTitle (title) {
      const titleDOM = document.getElementById('title')
      titleDOM.innerHTML = title.text
      titleDOM.style.color = title.color
    }
    
    function renderContent (content) {
      const contentDOM = document.getElementById('content')
      contentDOM.innerHTML = content.text
      contentDOM.style.color = content.color
    }
    
    let appState = {
      title: {
        text: 'React.js 小书',
        color: 'red',
      },
      content: {
        text: 'React.js 小书内容',
        color: 'blue'
      }
    }
    
    function stateChanger (state, action) {
      switch (action.type) {
        case 'UPDATE_TITLE_TEXT':
          state.title.text = action.text
          break
        case 'UPDATE_TITLE_COLOR':
          state.title.color = action.color
          break
        default:
          break
      }
    }
    
    const store = createStore(appState, stateChanger)
    store.subscribe(() => renderApp(store.getState())) // 监听数据变化
    
    renderApp(store.getState()) // 首次渲染页面
    store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
    store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色

    纯函数:一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。这么说肯定比较抽象,我们把它掰开来看:

    const a = 1

    const foo = (b) => a + b

    foo(2) // => 3

    let appState = {
      title: {
        text: 'React.js 小书',
        color: 'red',
      },
      content: {
        text: 'React.js 小书内容',
        color: 'blue'
      }
    }
    
    function stateChanger (state, action) {
      switch (action.type) {
        case 'UPDATE_TITLE_TEXT':
          return {
            ...state,
            title: {
              ...state.title,
              text: action.text
            }
          }
        case 'UPDATE_TITLE_COLOR':
          return {
            ...state,
            title: {
              ...state.title,
              color: action.color
            }
          }
        default:
          return state
      }
    }
    
    const store = createStore(appState, stateChanger)

    其实 appState 和 stateChanger 可以合并到一起去:stateChanger 这个玩意起一个通用的名字:reducer,

    function stateChanger (state, action) {
      if (!state) {
        return {
          title: {
            text: 'React.js 小书',
            color: 'red',
          },
          content: {
            text: 'React.js 小书内容',
            color: 'blue'
          }
        }
      }
      switch (action.type) {
        case 'UPDATE_TITLE_TEXT':
          return {
            ...state,
            title: {
              ...state.title,
              text: action.text
            }
          }
        case 'UPDATE_TITLE_COLOR':
          return {
            ...state,
            title: {
              ...state.title,
              color: action.color
            }
          }
        default:
          return state
      }
    }
    stateChanger 现在既充当了获取初始化数据的功能,也充当了生成更新数据的功能。如果有传入 state 就生成更新数据,否则就是初始化数据。这样我们可以优化 createStore 成一个参数,因为 state 和 stateChanger 合并到一起了:
    
    function createStore (reducer) {
      let state = null
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)
      const getState = () => state
      const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
      }
      dispatch({}) // 初始化 state
      return { getState, dispatch, subscribe }
    }

    reducer 是不允许有副作用的。你不能在里面操作 DOM,也不能发 Ajax 请求,更不能直接修改 state,它要做的仅仅是 —— 初始化和计算新的 state

    connect 只依赖于外界传进去的 props 和自己的 state,而并不依赖于其他的外界的任何数据的组件,类似纯函数

    高阶组件帮助我们从 context 取数据,我们也需要写 Dumb 组件帮助我们提高组件的复用性。所以我们尽量多地写 Dumb 组件,然后用高阶组件把它们包装一层,高阶组件和 context 打交道,把里面数据取出来通过 props 传给 Dumb 组件。

    mapStateToProps、mapDispatchToProps
    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    
    export const connect = (mapStateToProps) => (WrappedComponent) => {
      class Connect extends Component {
        static contextTypes = {
          store: PropTypes.object
        }
    
        render () {
          const { store } = this.context
          let stateProps = mapStateToProps(store.getState())
          // {...stateProps} 意思是把这个对象里面的属性全部通过 `props` 方式传递进去
          return <WrappedComponent {...stateProps} />
        }
      }
    
      return Connect
    }

    connect 函数接受一个组件 WrappedComponent 作为参数,把这个组件包含在一个新的组件 Connect 里面,Connect 会去 context 里面取出 store。
    现在要把 store 里面的数据取出来通过 props 传给 WrappedComponent。
    由于传个每个组件的store不一样,还需要告诉我们需要什么数据,高阶组件,才会去获取,为了解决这个问题,才有mapStateToProps函数

    
    
    ...
    const mapStateToProps = (state) => {
      return {
        themeColor: state.themeColor
      }
    }
    Header = connect(mapStateToProps)(Header)
    ...
    //和 mapStateToProps 一样,它返回一个对象,这个对象内容会同样被 connect 当作是 props 参数传给被包装的组件。
    // 不一样的是,这个函数不是接受 state 作为参数,而是 dispatch,你可以在返回的对象内部定义一些函数,这些函数会用到 dispatch 来触发特定的 action。
    //调整 connect 让它能接受这样的 mapDispatchToProps export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { allProps: {} } } componentWillMount () { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps () { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {} // 防止 mapStateToProps 没有传入 let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {} // 防止 mapDispatchToProps 没有传入 this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
    const mapStateToProps = (state) => {
      return {
        themeColor: state.themeColor
      }
    }
    const mapDispatchToProps = (dispatch) => {
      return {
        onSwitchColor: (color) => {
          dispatch({ type: 'CHANGE_COLOR', themeColor: color })
        }
      }
    }
    ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)

     Provider: 其实它要用 context 就是因为要把 store 存放到里面,好让子组件 connect 的时候能够取到 store。我们可以额外构建一个组件来做这种脏活,然后让这个组件成为组件树的根节点,那么它的子组件都可以获取到 context 了,我们把这个组件叫 Provider,容器组件

    xport class Provider extends Component {
      static propTypes = {
        store: PropTypes.object,
        children: PropTypes.any
      }
    
      static childContextTypes = {
        store: PropTypes.object
      }
    
      getChildContext () {
        return {
          store: this.props.store
        }
      }
    
      render () {
        return (
          <div>{this.props.children}</div>
        )
      }
    }

    原文出处

  • 相关阅读:
    logback学习二
    logback学习
    弱类型、强类型、动态类型、静态类型语言的区别
    BlockingQueue
    ExecutorService
    Future学习
    SetTimeout()多次运行函数后越来越快的问题
    LISTAGG函数
    Oracle字段
    使用powerdesigner进行数据库设计
  • 原文地址:https://www.cnblogs.com/chenzxl/p/13462832.html
Copyright © 2011-2022 走看看