动手实现一个mini-redux

98 阅读13分钟

动手实现一个redux

获取数据

现在定义了三个组件名为ComponentOne、ComponentTwo、ComponentThree 在组件一中展示数据、组件二用来修改数据

const ComponentOne = () => <section>组件一</section>
const ComponentTwo = () => <section>组件二</section>

那么数据从哪里来的呢? 使用的是react的上下文 先简单介绍一下context,context是react 16.3中新增特性。用来解决传递props时需要使用的组件比较深层次,那么要从父组件一直传递,期间无需用到props的组件也需要接收props。而context可以直接将数据传递到需要使用数据的组件不需要再层层传递。

// 创建一个context,这里可以给它值或不给。
const MyContext = createContext(null)
// 如何提供数据?
// 直接像普通的组件一样使用,不过需要加 .Provider 和value
<MyContext.Provider value="dark">
  <Son>
</MyContext.Provider>
// 如何获取数据?
// 使用 useContext并传入创建的context,返回值即使需要使用的数据。
function Son() {
  const theme = useContext(MyContext)
  return <div>{theme}</div>
}

现在使用Context来获取数据

function App () {
  const [appState, setAppState] = useState({
    user: { name: "fishworm", age: 18 }
  })
  const contextValue = { appState, setAppState }

  return (
    <appContext.Provider value={contextValue}>
      <ComponentOne />
      <ComponentTwo />
      <ComponentThree />
    </appContext.Provider>
  )
}

组件一使用contextValue来获取数据

const ComponentOne = () => <section>组件一<User /></section>

const User = () => {
  const contextValue = useContext(appContext)
  return <div>User: {contextValue.appState.user.name}</div>
}

组件二使用 contextValuesetAppState 来修改数据

const ComponentTwo = () => <section>组件二<UserModifier /></section>

const UserModifier = () => {
  const contextValue = useContext(appContext)
  const { appState, setAppState } = contextValue
  const onChange = e => {
    appState.user.name = e.target.value
    setAppState({ ...contextValue.appState })
  }

  return <div>
    <input value={contextValue.appState.user.name}
      onChange={onChange}
    />
  </div>
}

完整代码

import { useState, useContext, createContext } from "react"

const appContext = createContext(null)

function App () {
  const [appState, setAppState] = useState({
    user: { name: "fishworm", age: 18 }
  })
  const contextValue = { appState, setAppState }

  return (
    <appContext.Provider value={contextValue}>
      <ComponentOne />
      <ComponentTwo />
      <ComponentThree />
    </appContext.Provider>
  )
}

const ComponentOne = () => <section>组件一<User /></section>
const ComponentTwo = () => <section>组件二<UserModifier /></section>
const ComponentThree = () => <section>组件三</section>

const User = () => {
  const contextValue = useContext(appContext)
  return <div>User: {contextValue.appState.user.name}</div>
}

const UserModifier = () => {
  const contextValue = useContext(appContext)
  const { appState, setAppState } = contextValue
  const onChange = e => {
    appState.user.name = e.target.value
    setAppState({ ...contextValue.appState })
  }

  return <div>
    <input value={contextValue.appState.user.name}
      onChange={onChange}
    />
  </div>
}

export default App

规范创建流程

上面的 appState 创建流程不规范,现在按照redux中的reducer的使用方法来规范一下创建数据。

const reducer = (state, { type, payload }) => {
  if (type === 'updateUser') {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload
      }
    }
  }

  return state
}
const UserModifier = () => {
  const contextValue = useContext(appContext)
  const { appState, setAppState } = contextValue
  const onChange = e => {
    appState.user.name = e.target.value
-   setAppState({ ...contextValue.appState })
+   setAppState(reducer(appState, { type: 'updateUser', payload: { name: e.target.value } }))
  }

  return <div>
    <input value={contextValue.appState.user.name}
      onChange={onChange}
    />
  </div>
}

使用dispatch规范setState流程

现在我们每次更新数据都要使用 setAppState(reducer()) 太繁琐了,有没有什么办法简化一下

setAppState(reducer(appState, { type: 'updateUser', payload: { name: e.target.value } }))
setAppState(reducer(appState, { type: 'updateAge', payload: { name: e.target.value } }))

使用dispatch简化 setState

const Wrapper = () => {
  const { appState, setAppState } = useContext(appContext)
  const dispatch = (action) => {
    setAppState(reducer(appState, action))
  }

  return <UserModifier dispatch={dispatch} state={appState} />
}

const UserModifier = ({ dispatch, state }) => {
  const onChange = e => {
    dispatch({ type: 'updateUser', payload: { name: e.target.value } })
  }

  return <div>
    <input value={state.user.name}
      onChange={onChange}
    />
  </div>
}
- const ComponentTwo = () => <section>组件二<UserModifier /></section>
+ const ComponentTwo = () => <section>组件二<Wrapper /></section>

将组件修改成可复用的高阶组件connect

上面代码虽然是优化了 setAppState(reducer()) ,但是这个组件只能在一个地方使用。现在这样在其他组件中就没办法获取到 dispatchstate,如果每个组件都套一层 Wrapper 太冗余了。所以需要修改一下

这里在简单介绍一下什么是高阶组件:就是接收一个组件作为参数,在不修改样式只修改逻辑的情况下返回组件。 了解了什么是高阶组件,我们就按照这个逻辑进行修改。

const connect = (Component) => {
  return (props) => {
    const {appState, setAppState} = useContext(appContext)
    const dispatch = (action) => {
      setAppState(reducer(appState, action))
    }
    return <Component {...props} dispatch={dispatch} state={appState} />
  }
}

使用高阶组件

// 这是他原来的样子
const UserModifier = ({ dispatch, state }) => {}
// 修改后的样子
const UserModifier = connect(({ dispatch, state, children }) => {
    const onChange = e => {
    dispatch({ type: 'updateUser', payload: { name: e.target.value } })
  }

  return <div>
    {children}
    <input value={state.user.name}
      onChange={onChange}
    />
  </div>
})
- const ComponentTwo = () => <section>组件二<Wrapper /></section>
+ const ComponentTwo = () => <section>组件二<UserModifier /></section>

精准渲染组件

// App.jsx
import { useState, useContext, createContext } from "react"

const appContext = createContext(null)

function App () {
  const [appState, setAppState] = useState({
    user: { name: "fishworm", age: 18 }
  })
  const contextValue = { appState, setAppState }

  return (
    <appContext.Provider value={contextValue}>
      <ComponentOne />
      <ComponentTwo />
      <ComponentThree />
    </appContext.Provider>
  )
}

const ComponentOne = () => {
  console.log('componentOne' + Math.random())
  return <section>组件一<User /></section>
}
const ComponentTwo = () => {
  console.log('componentTwo' + Math.random())
  return <section>组件二<UserModifier /></section>
}
const ComponentThree = () => {
  console.log('componentThree' + Math.random())
  return <section>组件三</section>
}

const User = () => {
  const contextValue = useContext(appContext)
  return <div>User: {contextValue.appState.user.name}</div>
}

const reducer = (state, { type, payload }) => {
  if (type === 'updateUser') {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload
      }
    }
  }

  return state
}

const connect = (Component) => {
  return (props) => {
    const { appState, setAppState } = useContext(appContext)
    const dispatch = (action) => {
      setAppState(reducer(appState, action))
    }
    return <Component {...props} dispatch={dispatch} state={appState} />
  }
}

const UserModifier = connect(({ dispatch, state, children }) => {
  console.log('UserModifier执行了' + Math.random())
  const onChange = e => {
    dispatch({ type: 'updateUser', payload: { name: e.target.value } })
  }

  return <div>
    {children}
    <input value={state.user.name}
      onChange={onChange}
    />
  </div>
})

export default App

给每个组件添加了 log 。现在有一个问题,使用 setAppState 修改内容后每个组件都会重新渲染。但是现在组件三并没有修改,我也不希望他重新渲染。只有几个组件时不会有什么问题,如果组件很多就会有性能问题。 这是什么原因造成的呢? 原因就是使用 useState 重新渲染后父组件会重新渲染,父组件重新渲染后子组件也会重新渲染。 那应该怎么办? 使用 useMemo 来优化

const x = useMemo = (() => {
  return <ComponentThree>
}, [appState.user.age])

    <appContext.Provider value={contextValue}>
      <ComponentOne />
      <ComponentTwo />
      {x}
    </appContext.Provider>

这样修改虽然可以,但是每个组件都要使用 useMemo 还是太麻烦了。有没有其他办法,用到 user 时才会重新执行。 现在重新修改一下 state

const appContext = createContext(null)

const store = {
  state: {
    user: { name: 'fishworm', age: 18 }
  },
  setState: (newState) {
    store.state = newState
  },
}

function App () {
  return (
    <appContext.Provider value={store}>
      <ComponentOne />
      <ComponentTwo />
      <ComponentThree />
    </appContext.Provider>
  )
}

const User = () => {
  const state = useContext(appContext)
  return <div>User: {state.user.name}</div>
}

const connect = (Component) => {
  return (props) => {
    const { state, setState } = useContext(appContext)
    const dispatch = (action) => {
      setState(reducer(appState, action))
    }
    return <Component {...props} dispatch={dispatch} state={state} />
  }
}

修改后就不使用 useState 重新渲染了,虽然修改代码后不会重新渲染多余的组件了,但是需要重新渲染的组件也不渲染了。 这个问题简单。

const connect = (Component) => {
  return (props) => {
    const { state, setState } = useContext(appContext)
    // 使用 useState重新渲染当前组件
    const [, update] = useState({})
    const dispatch = (action) => {
      setState(reducer(appState, action))
      // 使用 useState重新渲染当前组件
      update({})
    }
    return <Component {...props} dispatch={dispatch} state={state} />
  }
}

因为每个对象的引用是不一样的,在 connect 中使用 useState 就会重新渲染当前组件。 虽然当前修改的组件会重新渲染了,但是展示的组件 组件一 没有重新渲染。 这里我们可以用到发布订阅模式来进行修改。

export const store = {
  state: {
    user: { name: 'fishworm', age: 18 }
  },
  setState (newState) {
    store.state = newState
    // 需要重新渲染的组件执行渲染
    // 这里放不放store.state都行,放了方便取出新的state
    store.listeners.map(fn => fn(store.state))
  },
  // 订阅中心
  listeners: [],
  subscribe (fn) {
    store.listeners.push(fn)
    // 取消订阅
    return () => {
      const index = store.listeners.indexOf(fn)
      store.listeners.splice(index, 1)
    }
  }
}

export const connect = (Component) => {
  return (props) => {
    const { state, setState } = useContext(appContext)
    const [, update] = useState({})
    useEffect(() => {
      // 重新渲染
      store.subscribe(() => {
        update({})
      })
    }, [])
    const dispatch = (action) => {
      setState(reducer(state, action))
    }
    return <Component {...props} dispatch={dispatch} state={state} />
  }
}

抽离 redux 逻辑 完整代码

// App.jsx
import React from 'react'
import { appContext, store, connect } from './redux.jsx'

function App () {
  return (
    <appContext.Provider value={store}>
      <ComponentOne />
      <ComponentTwo />
      <ComponentThree />
    </appContext.Provider>
  )
}

const ComponentOne = () => {
  console.log('componentOne' + Math.random())
  return <section>组件一<User /></section>
}
const ComponentTwo = () => {
  console.log('componentTwo' + Math.random())
  return <section>组件二<UserModifier /></section>
}
const ComponentThree = () => {
  console.log('componentThree' + Math.random())
  return <section>组件三</section>
}

const User = connect(({ state, dispatch }) => {
  console.log('User执行了' + Math.random())
  return <div>User: {state.user.name}</div>
})



const UserModifier = connect(({ dispatch, state, children }) => {
  console.log('UserModifier执行了' + Math.random())
  const onChange = e => {
    dispatch({ type: 'updateUser', payload: { name: e.target.value } })
  }

  return <div>
    {children}
    <input value={state.user.name}
      onChange={onChange}
    />
  </div>
})

export default App
// redux.jsx
import React, { useContext, useEffect, useState } from 'react'

export const store = {
  state: {
    user: { name: 'fishworm', age: 18 }
  },
  setState (newState) {
    store.state = newState
    store.listeners.map(fn => fn(store.state))
  },
  listeners: [],
  subscribe (fn) {
    store.listeners.push(fn)
    return () => {
      const index = store.listeners.indexOf(fn)
      store.listeners.splice(index, 1)
    }
  }
}

export const reducer = (state, { type, payload }) => {
  if (type === 'updateUser') {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload
      }
    }
  }

  return state
}

export const connect = (Component) => {
  return (props) => {
    const { state, setState } = useContext(appContext)
    const [, update] = useState({})
    useEffect(() => {
      store.subscribe(() => {
        update({})
      })
    }, [])
    const dispatch = (action) => {
      setState(reducer(state, action))
    }
    return <Component {...props} dispatch={dispatch} state={state} />
  }
}

export const appContext = React.createContext(null)

connect 接受 selector

先看下需要实现什么功能,在 redux 中的 connect 执行的第一个括号可以接收一个 selector 参数,这个参数就是在 state 中的变量,在第二个括号中就能获取到第一个括号中返回的值。 这解决了什么问题呢? 如果要获取值得名称很长,那么用这个方法就可以简化。

// 比如
const User = connect(({state, dispatch}) => {
  return <div>{{state.xxx.xxx.xxx.name}}</div>
})
// 解决
const User = connect((state) => {
  return {name: state.xxx.xxx.xxx.name}
})(({name}) => {
  return <div>{{name}}</div>
})

现在刷要执行两次函数,并且第一个函数需要获取一个selector参数,第二个函数获取Component参数

const connect = (selector) => (Component) => {
    return <Component {...props} {...data} dispatch={dispatch} />
}

在这个基础上第一个函数的返回值是第二个函数的参数

const connect = (selector) => (Component) => {
  // 获取到参数,如果有参数的返回处理后的参数,没有参数就返回原始值
  const data = selector ? selector(state) : { state }
  return <Component {...props} {...data} dispatch={dispatch} />
}

看下完整代码

修改后的 connect

export const connect = (selector) => (Component) => {
  return (props) => {
    const { state, setState } = useContext(appContext)
    const [, update] = useState({})
    const data = selector ? selector(state) : { state }
    useEffect(() => {
      store.subscribe(() => {
        update({})
      })
    })
    const dispatch = (action) => {
      setState(reducer(state, action))
    }
    return <Component {...props} {...data} dispatch={dispatch} />
  }
}

使用 connect

const User = connect((state) => {
  return {user: state.user}
})(({user}) => {
  console.log('User执行了' + Math.random())
  return <div>User: {user.name}</div>
})

selector的精准渲染

selector的精准渲染是什么意思? 组件只在自己的数据变化时渲染

要做什么呢? 只用判断一下数据有没有修改过

const new Data = selector ? selector(store.state) : { state: store.state }
// 主要是这里
if (changed(data, newData)) {
  update({})
}

changed做了什么? 主要把两个对象遍历出来挨个判断,如果相等说明没有修改,不相等说明修改了就重新执行渲染。

const changed = (oldState, newState) => {
  let changed = false
  for (let key in oldState) {
    if (oldState[key] !== newState[key]) {
      changed = true
    }
  }
  return changed
}
useEffect(() => {
  store.subscribe(() => {
  const newData = selector
    ? selector(state)
    : { state: state }
  if (changed(data, newData)) {
    update({})
  }
})
}, [selector])

结束了吗? 还没有,这里还需要取消订阅。如果不取消订阅,给useEffect添加第二个依赖state每次修改都会多次渲染。当然我们不需要给它添加第二个依赖

store.subscribe的返回值就是取消订阅的函数,执行这个函数即可

useEffect(() => {
  const unsubscribe = store.subscribe(() => {
  const newData = selector
    ? selector(state)
    : { state: state }
  if (changed(data, newData)) {
    update({})
  }
})
  return unsubscribe
}, [selector])

// 这里可以将它简写
useEffect(() => {
  return store.subscribe(() => {
  const newData = selector
    ? selector(state)
    : { state: state }
  if (changed(data, newData)) {
    update({})
    }
  })
}, [selector])

// 再进一步简写
useEffect(() => store.subscribe(() => {
  const newData = selector
    ? selector(state)
    : { state: state }
  if (changed(data, newData)) {
    update({})
    }
}), [selector])

现在以组件三为例使用

// App.jsx
const ComponentThree = connect(state => {
  return { group: state.group }
})(({ group }) => {
  console.log('componentThree' + Math.random())
  return <section>组件三<div>Group: {group.name}</div></section>
})

添加mapDispatchToProps

mapDispatchToProps有什么用呢?

// 修改前
const UserModifier = connect()(({ dispatch, state, children }) => {
  console.log('UserModifier执行了' + Math.random())
  const onChange = e => {
    dispatch({ type: 'updateUser', payload: { name: e.target.value } })
  }
  ...
}

// 修改后
const UserModifier = connect(null, (dispatch) => {
  return {
    updateUser: (attrs) => dispatch({type: 'updateUser', payload: attrs})
  }
})(({ updateUser, state, children }) => {
  console.log('UserModifier执行了' + Math.random())
  const onChange = e => {
    updateUser({ name: e.target.value })
  }
  ...
})

这里做了什么? 就是向connect的第二个函数传入一个函数,函数返回一个修改数据的的函数。 为什么要这么做?解决了什么? 这么做主要是修改更新数据过程,看起来更好看了些。

主要做了什么? 主要就是需要判断dispatchSelector是否存在,如果存在就往dispatchSelector中传入需要更新数据的函数。

const connect = (selector, dispatchSelector) => (Component) => {
  const dispatch = (action) => {
    setState(reducer(state, action))
  }
  const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : { dispatch }
}

来看下完整的实现

export const connect = (selector, dispatchSelector) => (Component) => {
  return (props) => {
    const dispatch = (action) => {
      setState(reducer(state, action))
    }
    const { state, setState } = useContext(appContext)
    const [, update] = useState({})
    const data = selector ? selector(state) : { state }
    const dispatchers = dispatchSelector
      ? dispatchSelector(dispatch)
      : { dispatch }
    useEffect(() => store.subscribe(() => {
        const newData = selector
          ? selector(store.state)
          : { state: store.state }
        if (changed(data, newData)) {
          update({})
        }
      }), [selector])
    return <Component {...props} {...data} {...dispatchers} />
  }
}
// 使用
const UserModifier = connect(null, (dispatch) => {
  // 返回 updateUser
  return {
    updateUser: (attrs) => dispatch({type: 'updateUser', payload: attrs})
  }
})(({ updateUser, state, children }) => {
  // 接收到 updateUser 这个函数
  console.log('UserModifier执行了' + Math.random())
  const onChange = e => {
    updateUser({ name: e.target.value })
  }

  ...
})

添加connectToUser

经过上面一顿操作,实现了一个简单的redux,但是现在发现有重复的代码出现。

// 比如都需要使用`connect`然后执行两个函数
const User = connect((state) => {
  return { user: state.user }
})(({ user }) => {
  ...
}

const UserModifier = connect(null, (dispatch) => {
  return {
    updateUser: (attrs) => dispatch({type: 'updateUser', payload: attrs})
  }
})(({ updateUser, state, children }) => {
  ...
}

现在就需要把代码抽离出来复用

创建一个connecters的文件用来处理User的逻辑

// connectToUser.jsx
import { connect } from '../redux'
// 读数据
const userSelector = state => {
  return { user: state.user }
}
// 写数据
const userDispatcher = (dispatch) => {
  return {
    updateUser: (attrs) => dispatch({type: 'updateUser', payload: attrs})
  }
}

export const connectToUser = connect(userSelector, userDispatcher)
// 抽离前的代码
// 直接使用 connect
const User = connect((state) => {
  return { user: state.user }
})(({ user }) => {
  console.log('User执行了' + Math.random())
  return <div>User: {user.name}</div>
})

const UserModifier = connect(null, (dispatch) => {
  return {
    updateUser: (attrs) => dispatch({type: 'updateUser', payload: attrs})
  }
})(({ updateUser, state, children }) => {
  console.log('UserModifier执行了' + Math.random())
  const onChange = e => {
    updateUser({ name: e.target.value })
  }

  return <div>
    {children}
    <input value={state.user.name}
      onChange={onChange}
    />
  </div>
})

// 抽离后的代码
// 使用抽离的 connectToUser
const User = connectToUser(({ user }) => {
  console.log('User执行了' + Math.random())
  return <div>User: {user.name}</div>
})

const UserModifier = connectToUser(({ updateUser, user, children }) => {
  console.log('UserModifier执行了' + Math.random())
  const onChange = e => {
    updateUser({ name: e.target.value })
  }

  return <div>
    {children}
    <input value={user.name}
      onChange={onChange}
    />
  </div>
})

封装Provider和createStore

现在发现了一个问题,state的数据和reducer的逻辑都是写死的,需要修改成动态的。 把statereducer都置空 state: undefined, reducer: undefined 使用函数来修改

export const store = {
  state: undefined,
  reducer: undefined,
  setState (newState) {
    store.state = newState
    store.listeners.map(fn => fn(store.state))
  },
  listeners: [],
  subscribe (fn) {
    store.listeners.push(fn)
    return () => {
      const index = store.listeners.indexOf(fn)
      store.listeners.splice(index, 1)
    }
  }
}

创建store

export const createStore = (reducer, initState) => {
  store.state = initState
  store.reducer = reducer
  return store
}

拆解reducer与初始数据

// 通过传入2个参数第一个是 reducer,第二个是初始数据
const store = createStore((state, { type, payload }) => {
  if (type === 'updateUser') {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload
      }
    }
  }
  return state
}, {
  user: { name: 'fishworm', age: 18 },
  group: { name: '前端组' }
})

这样看是不是有点不清晰,再把他们拆开。

const reducer = (state, { type, payload }) => {
  if (type === 'updateUser') {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload
      }
    }
  }
  return state
}

const initState = {
  user: { name: 'fishworm', age: 18 },
  group: { name: '前端组' }
}

const store = createStore(reducer, initState)

创建Provider

我们发现redux不是使用appContext.Provider这样使用的有没有办法修改成redux的样子? 可以的,只用修改一下

export const Provider = ({store, children}) => {
  return (
    <appContext.Provider value={store}>
      {children}
    </appContext.Provider>
  )
}
function App () {
  return (
    <Provider store={store}>
      <ComponentOne />
      <ComponentTwo />
      <ComponentThree />
    </Provider>
  )
}

总结

首先我们有一个App它里面会有其他组件,我们需要做的就是让它访问到一个全局的state

第一个概念state

state就是存储数据用的,我们发现redux中的state是放在store中。 那我们如何将state与组件连接起来呢?react-redux中使用connect将组件与state连接起来,它提供了读与写功能。如果要读就从组件的属性中获取state,如果要写就在组件中获取dispatch来修改数据。如果要精确的读就传入selector,如果要精确的写就使用mapDispatchToProps函数返回的updateUser这个自己封装的apireact-redux所要解决的问题就是将组件与state连接,并提供两个读写api

第二个概念connect

connect做的是对组件做了一次封装,封装成一个Wrapper并返回出去。 主要做了三件事情

  • 1、从上下文中获取到statesetState。不过不用上下文用store也可以。
  • 2、得到具体的数据和具体的dispatch
  • 3、在恰当的时间进行更新,对store进行订阅。一旦发现store变化,就在数据更的情况下使用update进行更新。

然后dispatch又拆分为reducerinitStateaction reducer:在本篇中是用于规范创建state的过程,为了不修改原来的state。 initState: 初始化state。 action: 对一次变动的描述。如:type、payload

Api重构

redux中暴露的ApigetState()、dispatch(action)、subscribe(listener) 现在重构成和redux相同,本质还是隐藏起来不暴露出去所有的属性

// getState
// 原来
export const store = {
    state: {
    user: undefined,
    group: undefined
  }
}

// 重构
let state = undefined
export const store = {
  getState() {
    return state
  }
}
// dispatch
// 原来
export const store = {
    setState (newState) {
    store.state = newState
    store.listeners.map(fn => fn(store.state))
  }
}
export const createStore = (reducer, initState) => {
  store.state = initState
  store.reducer = reducer
  return store
}
const dispatch = (action) => {
  setState(store.reducer(state, action))
}

// 重构
let reducer = undefined
const setState = (newState) => {
  state = newState
  listeners.map(fn => fn(state))
}
export const store = {
  dispatch: (action) => {
    setState(reducer(state, action))
  }
}
const dispatch = store.dispatch
export const createStore = (_reducer, initState) => {
  state = initState
  reducer = _reducer
  return store
}
// subscribe
// 原来
export const store = {
  listeners: [],
  subscribe (fn) {
    store.listeners.push(fn)
    return () => {
      const index = store.listeners.indexOf(fn)
      store.listeners.splice(index, 1)
    }
  }
}

// 重构
let listeners = []
export const store = {
  subscribe (fn) {
    listeners.push(fn)
    return () => {
      const index = listeners.indexOf(fn)
      listeners.splice(index, 1)
    }
  }
}

改写dispatch以支持函数action

先看下要实现什么功能

const ajax = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({data: {name: '3秒后的fishworm'}})
    }, 3000)
  })
}

const fetchUser = (dispatch) => {
  ajax('/user').then(response => {
    dispatch({type: 'updateUser', payload: response.data})
  })
}

const UserModifier = connect(null, null)(({ state, dispatch }) => {
  console.log('UserModifier执行了' + Math.random())
  const onClick = e => {
    // updateUser({ name: e.target.value })
    dispatch(fetchUser)
  }

  return <div>
    <div>User: {state.user.name}</div>
    <button onClick={onClick}>异步获取</button>
  </div>
})

就是简化异步请求,让看起来更优雅。

怎么做呢?

let dispatch = store.dispatch
// 保存老的dispatch
const prevDispatch = dispatch

dispatch = (action) => {
  // 如果传入的是一个函数就传一个新的dispatch
  if (action instanceof Function) {
    action(dispatch)
  } else {
    // 如果不是函数,直接传给老的dispatch修改
    prevDispatch(action)
  }
}

改写dispatch以支持payload为promise的action

有时候payload传入的可能是一个promise

const fetchUserPromise = () => {
  return ajax('/user').then(response => response.data)
}

const fetchUser = (dispatch) => {
  return ajax('/user').then(response => dispatch({type: 'updateUser', payload: response.data}))
}

const UserModifier = connect(null, null)(({ state, dispatch }) => {
  console.log('UserModifier执行了' + Math.random())
  const onClick = e => {
    dispatch({
      type: 'updateUser',
      payload: fetchUserPromise()
    })
    // dispatch(fetchUser)
  }
  ...
}

应该如何修改呢?

const prevDispatch2 = dispatch

dispatch = (action) => {
  // 判断传入的是否为promise
  if (action.payload instanceof Promise) {
    // 递归传入异步参数
    action.payload.then(data => {
      dispatch({...action, payload: data})
    })
  } else {
    prevDispatch2(action)
  }
}

总结: 其实就是改变了组织形式,没有对代码做任何优化。

阅读 redux-thunk 和 redux-promise 源码

redux是不支持函数和payloadpromise,所以它就让我们自己添加一个中间件applyMiddleware const store = createStore(reducer, initState, applyMiddleware(reduxThunk, reduxPromise))

// redux-thunk
function createThunkMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
  const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
    ({ dispatch, getState }) =>
    next =>
    action => {
      if (typeof action === 'function') {
        return action(dispatch, getState, extraArgument)
      }
      return next(action)
    }
  return middleware
}

export const thunk = createThunkMiddleware()

是不是和我们写的很像,它使用typeof来判断函数,我们用instanceof来判断。如果不是函数,就调用next函数。可以是下一个中间件或dispatch

// redux-promise
export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action) ? action.then(dispatch) : next(action);
    }
    // 判断是否为promise
    return isPromise(action.payload)
      ? action.payload
          .then(result => dispatch({ ...action, payload: result }))
          .catch(error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          })
      : next(action);
  };
}

如果是promise就把payload放到then里面然后把结果放到payload的里面吗,不是promise就执行下一个中间件。