# Redux/React-Redux

# 概念和组成

  • redux 包含 store, state, reducer, action, action creator这些概念

其中store可以理解为redux整体部分,下面包含了reducer, state, action这几部分,action creator是用来创建action

redux流程

# 初始化 store

redux初始化, 即创建store, 调用createStore方法创建一个store

import { createStore } from 'redux'
// createStore方法,接收的第一个参数是一个函数
// 传入参数函数是reducer
const store = createStore(reducer)
// store对象提供了三个方法
store.getState()
store.dispatch()
store.subscribe()

# 创建 reducer

reducer是个函数,接收两个参数,一个是初始state,一个是action

reducer函数return出一个新的state,传递给store用于改变state

reducer是一个无副作用的纯函数

function reducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

为什么叫 reducer,因为 reducer 可以作为 数组的 reduce()方法 的第一个参数

reduce 接收两个参数,第一个是个回调函数,第二个是初始值

其中回调函数接收四个参数,accumulator(上次计算出的值/累加值), currentValue(当前传递的值/item),index(下标,可选),array(原数组,可选)

在下面这个例子中,accumulatorstatecurrentValueaction

function reducer(state = 0, action) {
  switch (action.type) {
    case 'ADD':
      return state + action.payload // 执行一次,并返回accumulator
    default:
      return state
  }
}
const actions = [
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 },
]

actions.reduce(reducer, 0) // 输出3

# action && store.dispatch()

reducer 里面定义了初始 state 和改变 state 的接口,我们只需要传入对应的 action 即可,如上面 reducer 所示

一个 reducer 需要传递 state 以及 action,把这两个部分分解开来

用户在 view 层如何把 action 传给 reducer,就要用到 store 提供的 dispatch 方法了

// dispatch接收一个对象参数,也就是action,action必须要有type属性
// action是一个对象,其中必须要包含type属性,其他属性可以随意定义,只要在reducer有对应处理即可
const action = {
  type: 'INCREMENT',
}
store.dispatch(action)

现在我们要想 reducer 根据我们传递的 action 参数里面的值进行相应改变,如下
组件

// 传入的action
store.dispatch({
  type: 'INCREMENT',
  payload: {
    // 这里不一定是payload,只要和reducer约定好即可,payload也不一定要是对象
    number: 3,
  },
})

store.js

import { createStore } from 'redux'

function reducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + action.payload.number
    default:
      return state
  }
}

const store = createStore(reducer)

这样 state 可以依据 action 的 type 以及 payload 参数,进行相应变化

# action creator

如果每次用户都在 view 层写这些store.dispatch({}),一方面,我们不知道用户到底 dispatch 哪些 action,另一方面,action 写起来比较繁琐,并且还有可能在多处调用相同的 dispatch 行为 所以我们可以定义 action creator,即一个个函数,这些函数会返回 action

// 可以直接在reducer所在的文件进行定义
function increment(number) {
  return {
    type: 'INCREMENT',
    payload: { number },
  }
}
// 以下是在view调用
import store from './store'
import { increment } from './actionCreator'
// increment函数方法传入一个number参数
store.dispatch(increment(3))

action creator 和 action 都可以通过store.dispatch()直接调用,只不过 action creator 封装了一个可以返回 action 的函数

# store.getState()

在 view 也就是用户页面如何获取 store 里面的 state,需要用到上面 store 提供的 getState 方法

import store from './store.js' // 这里为了方便,默认redux.js有暴露出的store对象
// getState方法不需要传参数
console.log(store.getState()) // 这里打印了 redux 的 state值

# 纯函数 reducer

reducer 函数的一个重要特征是,它是一个纯函数,也就是说,相同的输入,会得到相同的输出 和中学学的函数,如y=2x很相似,相同输入必有一样输出 在 reducer 里面不能直接重新给 state 赋值,而是返回一个新的 state,由 store 自己对 state 进行改变

function reducer(state, action) {
  return { ...state, ...newState } // newState如果包含state里面的一部分值,会覆盖,否则,会新建
  // 或者
  return Object.assign({}, state, { ...newState })
  // 也可以使用 JSON.parse(JSON.string(xxx)) 进行简单的深拷贝
}
// 如果state是一个数组
function reducer(state, action) {
  return [...state, newItem]
}

# store.subscribe()

store.subscribe()方法, 接收一个回调函数,在回调函数里我们做相应的处理 当 redux 的 state 改变时,会自动触发store.subscribe()方法 我们可以在 subscribe 方法处理我们的需求

let listenSubscribe = store.subscribe(() => {
  console.log('state发生了变化!')
  console.log('当前state', store.getState())
})

# reducer 的拆分

reducer 函数负责生成 state,由于整个应用只有一个 state 对象,包含所有数据,对于大型应用来说,这个 state 会很庞大,导致 reducer 函数也很庞大
这时候我们需要将 reducer 拆分成多个,对于不同页面或者不同功能,我们把它拆分成独立的部分 通过 redux 提供的 combineReducers 方法

import { createStore, combineReducers } from 'redux'
import { aReducer } from './aReducer.js'
import { bReducer } from './bReducer.js'

const totalReducer = combineReducers({
  aReducer,
  bReducer,
})

const store = createStore(totalReducer)
export default store

# 不使用 react-redux 缺点

  • 对于有状态的组件,分发 state 需要使用 store.getState api
  • 当需要更新 redux 的状态时,需要使用 store.subscribe 监听
  • 页面需要同步更新时,redux 里面的数据需要获取到并保存到组件自身 state 里面
  • 使用 redux 数据的组件必须要改成由状态组件

# 不使用中间件处理异步请求, 如 redux-thunk 或 redux-saga

在 view 层的某个函数内部(如在组件刚挂载时)处理异步请求,处理完异步请求并获取到结果之后再 dispatch 一个 action
缺点是,

  • 请求分散在很多 view 组件中,维护起来很麻烦
  • 如果有多个组件调用了这个请求,每个组件的 mapDispatchToProps 都需要写(如果使用了 react-redux 的话),
  • 小型项目可以像这样使用

# 使用 react-redux

第三方库 react-redux,该库提供了 Provider 和 connect
Provider 是一个普通的组件,可以作为顶层 app 状态的分发点,它只需要 store 属性就可以了,它会将 state 分发给所有被connect的组件
connect 是一个柯里化函数,先接收两个函数参数,mapStateToProps 和 mapDispatchToProps,再接收一个参数(将要绑定的组件本身)
mapStateToProps
mapDispatchToProps 参数是一个回调函数,参数是 dispatch,

# react-redux 分发状态与改变状态

思考:为什么使用 react-redux
这里参考官方文档:文档 (opens new window)

  • react-redux 是一个把 react 和 redux 绑定的库,redux 官方开发
  • 使用 redux 的组件必须要成为有状态组件,而使用了 react-redux 则可以成为无状态组件,state 通过 connect 进行分发,组件更容易复用
  • react-redux 内部进行了优化,可以避免不必要的 re-render

# 核心部分

  • Provider: 从外部封装了整个应用,并向 connect 模块传递 store
  • connect: 1.包装原组件,将 state 和 action 通过 props 的方式传到原组件内部 2.监听 store tree 的变化,使其包装的原组件可以响应 state 变化

# 不使用/使用 react-redux 分发 state 与提交 action 的方式区别

  • 状态分发:前者使用 store.getState(), 后者通过 connect 传递的第一个回调函数拿到
  • action 提交:前者通过 store.dispatch(action), 后者通过 connect 传递的第二个回调里面写 dispatch(action)

# 例子

TodoList.js ===== connect 部分

import { connect } from 'react-redux'

const TodoList = (props) => (
  <div>
    <div>
      <input placeholder={props.inputValue} onChange={props.inputChange} />
      <button onClick={props.clickButton}>提交</button>
    </div>
    <ul>
      {props.list.map((item, index) => {
        return <li key={index}>{item}</li>
      })}
    </ul>
  </div>
)

// store向下分发状态(state)
const stateToProps = (state) => {
  return {
    inputValue: state.inputValue,
    list: state.list,
  }
}

// 向reducer提交action
const dispatchToProps = (dispatch) => {
  return {
    inputChange(e) {
      let action = {
        type: 'change_input',
        value: e.target.value,
      }
      dispatch(action)
    },
    clickButton() {
      let action = { type: 'add_item' }
      dispatch(action)
    },
  }
}

export default connect(stateToProps, dispatchToProps)(TodoList)

App.js ==== Provide 部分

import React, { Component } from 'react'
import { Provider } from 'react-redux'
import TodoList from './TodoList'
import store from '../Store'

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <TodoList />
      </Provider>
    )
  }
}

export default App

# 参考

LastEditTime: 2020/4/26 16:20:30