Redux 在 React Hook 中的使用及其原理

共 10724字,需浏览 22分钟

 ·

2021-02-03 07:56

作者:黄刀小五
来源:SegmentFault 思否社区




浅谈Redux


下面将从what, why, how to 三个方面来说说Redux

第一问 what ❓ 什么是Redux


将一个web应用拆分成视图层与数据层, Redux就是保存其数据的一个容器, 其本质就是维护一个存储数据的对象.

  • State : 一个存放数据的容器 (一个对象)

const initState = {
  count: 0,
}

  • Action : 一个 want to do 的过程 (计划要做一个什么样的操作)

    • ActionType是对Action的描述, 也是连接Action和Reducer的桥梁
    • 本质上是一个由ActionType和payload(数据)组成的对象

export const increaseConstant = 'INCREASE' // ActionType

{
   type: increaseConstant,
   payload,
}  // Action

  • Reducer : 一个 to do 的过程 (执行Action计划的操作)

case increaseConstant: // 当 ActionType 为 'INCREASE' 时, 执行count++
  return {
    ...state,
    count: payload + 1
  }

第二问 why ❓ 为什么要使用Redux


当你不知道是否需要使用Redux的时候, 那就是不需要使用.

下面一组动图很好的描述了一个应用程序的开发过程, 及何时需要Redux.

  • 游戏初期阶段, 数据单向传递, 父传子

  • 游戏进入中期, 开始有少量非父子组件间需要通讯一些数据


  • 游戏进入后期, 开始需要大量的数据通讯


  • 此时, 就是Redux的用武之地了, 使用Redux后流程如下


第三问 how to ❓ 怎么使用Redux


在说使用方法之前, 我们先来从头到尾模拟一个Redux的过程, 只要理解原理, 使用起来那不是小菜一碟.

Let's go, come on baby!


下面我们就用一个简单的计数器的例子来模拟实现一个Redux的过程:

创建一个count组件


import React, { useState } from 'react'

const CountItem = (props) => {
  const {
    count,
    increase,
  } = props

  return (
    <>
      {count}
      <button onClick={increase}>Count++</button>
    </>
  )
}

const Count = () => {
  const [count, setCount] = useState(0)

  const increase = () => {
    setCount(count + 1)
  }

  return (
    <CountItem
      count={count}
      increase={increase}
    />
  )
}

export default Count

这样一个简单的count组件就创建好了, 现在, 我们想把对数据的操作单独封装成一个方法名为dispatch, 传递的参数为一个Action 格式: { type: xxx, payload: xxx }

封装一个Dispatch函数


const dispatch = (action) => {
  switch(action.type) {
    case 'INCREASE':
      return action.payload + 1
    default:
      break
  }
}

改写increase方法


const increase = () => {
-  setCount(count + 1)
+  setCount(dispatch({type'INCREASE', payload: count}))
}

这时, 我们将action对象也抽离出来, 方便复用, 新建action.js.

action.js => 返回action对象


const increaseCount = (payload) => {
  return {
    type'INCREASE',
    payload
  }
}

改写increase方法


const increase = () => {
-  setCount(dispatch({type'INCREASE', payload: count}))
+  setCount(dispatch(increaseCount(count)))
}

接下来我们把dispatch中的事件操作抽离到reducer中, 新建reducer.js.

reducer.js => 进行数据操作


const reducer = (state, action) => {
  const { type, payload } = action
  switch(type) {
    case 'INCREASE':
      return {
        ...state,
        count: payload + 1
      }
    default:
      return state
  }
}

改写dispatch函数


const dispatch = (action) => {
  const state = {
    count,
  }
  
  const newState = reducer(state, action)

  return newState
}

改写increase方法


const increase = () => {
-  setCount(dispatch(increaseCount(count)))
+  setCount(dispatch(increaseCount(count)).count)
}

接下来, 我们把set方法也拿到dispatch中, 让所有操作都在dispatch中完成.

继续改造dispatch函数, 增加setter做映射


const dispatch = (action) => {
  const state = {
    count,
  }
  
+  const setter = {
+    count: setCount
+  }
  
  const newState = reducer(state, action)
  
+  for (let key in newState) {
+    setter[key](newState[key])
+  }

-  return newState
}

改写increase方法


const increase = () => {
-  setCount(dispatch(increaseCount(count)).count)
+  dispatch(increaseCount(count))
}

这里我们可以看到, action.type是连接action和reducer的桥梁, 我们可以将actionType定义为常量单独保存.

在action中增加actionType


export const increaseConstant = 'INCREASE'

// 替换 action 和 reducer 中的 'INCREASE' 为 increaseConstant

基于现有场景, 如果我们有另一个功能, 而目前的reducer并不能帮助我们很好的把不同的功能划分开来, 我们改造一下reducer, 改造成一个对象, 用对象的key去区分功能.

改写reducer


const reducer = {
  count(state, action) {
    const { type, payload } = action
    switch(type) {
      case increaseConstant:
        return payload + 1
      default:
        break
    }
  },
}

这时我们要遍历reducer, 找到正确的key, 才能让程序正确执行, 我们新建combineReducers.js来完成这步操作.

combineReducers


const combineReducers = (reducer) => {
  return (state, action) => {
    let ret = {}

    for (let key in reducer) {
      ret[key] = reducer[key](state[key], action)
    }
    
    return {
      ...state,
      ...ret,
    }
  }
}

继续改下dispatch函数, 使其支持当前格式reducer.

改写dispatch


-  const newState = reducer(state, action)
+  const newState = combineReducers(reducer)(state, action)

至此, 一个redux的实现过程就完成了, 接下来, 我们实际用一用redux. 其实, 当完成上述操作的时候, 怎么用就已经说的差不多了.


Redux + React 使用


action, reducer, Count组件同上, Count组件需要简单改写下.

新建store.js

import { createStore, combineReducers } from 'redux'
import reducer from './recuder'

const initState = {
  count: 0,
}

const store = createStore(
  combineReducers(reducer),
  initState,
)

export default store

新建app.jsx, 引入store

import { Provider } from 'react-redux'
import store from './store'

const App = () => {
  return (
    <Provider store={store}>
      <Count />
    </Provider>
  )
}

export default App

改写Count组件

import React from 'react'
import { connect } from 'react-redux'
import { increaseCount } from './action'

const CountItem = (props) => {
  const {
    count,
    increase,
  } = props

  return (
    <>
      {count}
      <button onClick={increase}>Count++</button>
    </>
  )
}

const Count = (props) => {
  const {
    count,
    dispatch,
  } = props

  const increase = () => {
    dispatch(increaseCount(count))
  }

  return <CountItem count={count} increase={increase} />
}

export default connect(
  (state) => {
    return state
  },
  (dispatch) => {
    return { dispatch }
  }
)(Count)

接下来, 我们改写成hook的写法

改写Count组件

import React from 'react'
- import { connect } from 'react-redux'
+ import { useSelector, useDispatch } from 'react-redux'
import { increaseCount } from './action'

const CountItem = (props) => {
  const {
    count,
    increase,
  } = props

  return (
    <>
      {count}
      <button onClick={increase}>Count++</button>
    </>
  )
}

const Count = () => {
-  const {
-    count,
-    dispatch,
-  } = props
  
+  const count = useSelector(state => state.count)
+  const dispatch = useDispatch()

  const increase = () => {
    dispatch(increaseCount(count))
  }

  return <CountItem count={count} increase={increase} />
}

export default connect(
-  (state) => {
-      return state
-  },
-  (dispatch) => {
-    return { dispatch }
-  }
- )(Count)

export default Count

至此, 本篇文章就到此结束了, 大家可以写个复杂一点的组件练习下, 比如todoList, 手动滑稽. 逃了逃了。



点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,或扫描下方二维码添加“ SF 思否小姐姐 ”,回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -

浏览 36
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报