# Redux

Redux 就相当于Vue的Vuex数据层框架;原理基本也差不多。Redux = Reducer + Flux

具有以下原则:

  • store 是唯一的
  • 只有 store 能够改变自己的内容(在reducer中只是返回了新的store数据)
  • reducer 必须是纯函数(给定固定的输入,就一定会有固定的输出,而且不会有任何副作用)

代码如下:

// './store/index'
import { createStore } from 'redux'
import reducer from './reducer'

// 创建一个数据公共存储仓库;reducer真正的数据存储
const store = createStore(
  reducer,
  // 第二个参数是浏览器使用了redux调试工具  
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store


//-------------------------------- reducer.js

// 拿到之前的数据已经需要操作的数据的信息,进行处理数据,并返回给store
 const defaultState = {
   inputValue: '123',
   list: [1, 2]
 }
// state,所有store存储的数据; action
// reducer 可以接收state,但是绝不能修改state
export default (state = defaultState, action) => {
  console.log(state, action)
  if (action.type === 'change_input_value') {
    const newState =  JSON.parse(JSON.stringify(state))
    newState.inputValue = action.value
    return newState
  }
  if (action.type === 'add_toto_item' && state.inputValue !== '') {
    const newState =  JSON.parse(JSON.stringify(state))
    newState.list.push(newState.inputValue)
    newState.inputValue = ''
    console.log(newState)
    return newState
  }
  return state
}

// -----------------在组件中使用

import React, { Component } from 'react'
import 'antd/dist/antd.css'
import { Input, Button, List } from 'antd'
// 引入store数据仓库
import store from './store/index'
class AntaTodoList extends Component {
  constructor(props) {
    super(props)
    // store.getState获取store数据
    this.state = store.getState()
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleStoreChange = this.handleStoreChange.bind(this)
    this.handleBtnClick = this.handleBtnClick.bind(this)
    // 订阅store中的内容,store中的数据只要发生改变,里面的回调函数就会执行
    store.subscribe(this.handleStoreChange)
  }
  render() {
    return (
      <div style = {{ marginTop: '10px', marginLeft: '10px' }}>
        <div>
          <Input
            value = { this.state.inputValue }
            placeholder="Basic usage"
            style = {{ width: '300px', marginRight: '10px'}}
            onChange = { this.handleInputChange }
          />
          <Button type="primary" onClick = { this.handleBtnClick }>提交</Button>
        </div>
        <List
          style = {{ marginTop: '10px', width: '300px' }}
          bordered
          dataSource = { this.state.list }
          renderItem = { item => (
            <List.Item>
              { item }
            </List.Item>
          )}
        />
      </div>
    )
  }
  handleInputChange(e) {
    // 修改store中的inputValue
    const action = {
      type: 'change_input_value',
      value: e.target.value
    }
    // 将action传递给store,通过reducers处理数据,然后返回数据
    store.dispatch(action)
  }
  handleStoreChange() {
    console.log('change')
    // 当知道store中的数据变化的时候直接替换组件中state中的数据
    this.setState(store.getState())
  }
  handleBtnClick() {
    const action = {
      type: 'add_toto_item'
    }
    store.dispatch(action)
  }
}
export default AntaTodoList

流程是首先我们使用store中存储的数据,通过store.getState()获取到数据,然后input监听数据数据一旦变化,就修改store中的数据:store.dispatch(action), 然后在reducer中,进行判断操作类型type,进行修改对应的数据(数据通过深拷贝一份进行修改);然后在组件中监听store数据的变化,一旦变化重新通过store.getState()获取到数据进行更新组件中store中的数据,进行渲染。

上面的项目可以这样拆分:

// 组件
import React, { Component } from 'react'
import 'antd/dist/antd.css'
import { Input, Button, List } from 'antd'
// 引入store数据仓库
import store from './store/index'
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'
class AntaTodoList extends Component {
  constructor(props) {
    super(props)
    // store.getState获取store数据
    this.state = store.getState()
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleStoreChange = this.handleStoreChange.bind(this)
    this.handleBtnClick = this.handleBtnClick.bind(this)
    // 订阅store中的内容,store中的数据只要发生改变,里面的回调函数就会执行
    store.subscribe(this.handleStoreChange)
  }
  render() {
    return (
      <div style = {{ marginTop: '10px', marginLeft: '10px' }}>
        <div>
          <Input
            value = { this.state.inputValue }
            placeholder="Basic usage"
            style = {{ width: '300px', marginRight: '10px'}}
            onChange = { this.handleInputChange }
          />
          <Button type="primary" onClick = { this.handleBtnClick }>提交</Button>
        </div>
        <List
          style = {{ marginTop: '10px', width: '300px' }}
          bordered
          dataSource = { this.state.list }
          renderItem = { (item, index) => (
            <List.Item onClick = { this.handleItemDelete.bind(this, index) }>
              { item }
            </List.Item>
          )}
        />
      </div>
    )
  }
  handleInputChange(e) {
    // 修改store中的inputValue
    const action = getInputChangeAction(e.target.value)
    // 将action传递给store,通过reducers处理数据,然后返回数据
    store.dispatch(action)
  }
  handleStoreChange() {
    console.log('change')
    // 当知道store中的数据变化的时候直接替换组件中state中的数据
    this.setState(store.getState())
  }
  handleBtnClick() {
    const action = getAddItemAction()
    store.dispatch(action)
  }
  handleItemDelete(index) {
    const action = getDeleteItemAction(index)
    store.dispatch(action)
  }
}
export default AntaTodoList

// './store/index'
import { createStore } from 'redux'
import reducer from './reducer'

// 创建一个数据公共存储仓库;reducer真正的数据存储
const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)

export default store

// './store/actionCreators'
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
export const getInputChangeAction = value => {
  return {
    type: CHANGE_INPUT_VALUE,
    value
  }
}

export const getAddItemAction = () => {
  return {
    type: ADD_TODO_ITEM
  }
}

export const getDeleteItemAction = index => {
  return {
    type: DELETE_TODO_ITEM,
    index
  } 
}

// './actionTypes'

export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_TODO_ITEM = 'add_todo_item'
export const DELETE_TODO_ITEM = 'delete_todo_item'

# Redux-thunk 中间件使用发送请求

首先安装yarn add redux-thunk,使用:

import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'

// 创建一个数据公共存储仓库;reducer真正的数据存储
const store = createStore(
  reducer,
  applyMiddleware(thunk)
)

export default store

如果有多个中间件使用,比如之前使用的redux devtools跟thunk中间件需要一起使用: 这是redux devtools的一个解决方法

import { createStore, applyMiddleware, compose } from 'redux'
import reducer from './reducer'
// https://github.com/reduxjs/redux-thunk
import thunk from 'redux-thunk'
// https://github.com/zalmoxisus/redux-devtools-extension
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose

const enhancer = composeEnhancers(
  applyMiddleware(...[thunk]),
);
// 创建一个数据公共存储仓库;reducer真正的数据存储
const store = createStore(
  reducer,
  enhancer
)

export default store

使用redux-thunk,action不仅仅是一个对象,就像前面写的只返回type跟value的对象,可以 返回一个函数;然后我们调用store.dispatch(action)直接会执行这个返回的函数,然后在返回的函数里面我们去分发action,当action为返回 一个函数的时候,他会有一个参数dispatch分发,就相当于store.dispatch,我们就可以在返回的函数里面去分发action。 下面代码是使用thunk:

// 在组件中使用
  // import axios from 'axios'
  import {
    getTodoList 
  } from './store/actionCreators'
  componentDidMount() {
    // 如果发现action为一个函数,store.dispatch就会去执行一下这个函数,
    // 在这个函数里面我们去执行store.dispatch去分发请求,修改数据
    const action = getTodoList()
    store.dispatch(action) // 这里action返回的是一个函数

    // 如果不使用redux-thunk。我们需要在组件的声明周期里面去执行发送异步请求,
    // 如果有很多方法都需要在组件的生命周期里面去执行,这样会导致一个组件会很杂乱,不好维护。
    axios.get('/todolist.json').then(res => {
      console.log(res)
      const data = res.data
      const action = initListAction(data)
      store.dispatch(action)
    }).catch(e => {
      console.log(e)
    })
  }

  // './store/actionCreators'
// 获取异步请求的数据
// 返回一个函数
export const getTodoList = () => {
  // 当action返回为一个函数的时候,会接收到一个dispatch方法
  return (dispatch) => {
    axios.get('/todolist.json').then(res => {
      console.log(res)
      const data = res.data
      const action = initListAction(data)
      dispatch(action)
    }).catch(e => {
      console.log(e)
    })
  }
}

# Redux 中间件

Redux 中间件就是action与store之间的过程,我们通过函数处理了dispatch方法,再次封装。

# redux-saga 中间件的使用

https://github.com/redux-saga/redux-saga 首先进行安装:yarn add redux-saga,使用跟redux-thunk差不多,只不多他做了很多处理:

// 组件中
import {
  getInitList
} from './store/actionCreators'
  componentDidMount() {
    const action = getInitList()
    store.dispatch(action)
  }
// './store/actionCreators'
export const getInitList = () => {
  return {
    type: GET_INIT_LIST
  }
}
// 在store的配置:
// 使用redux-saga
import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga'
import todoSagas from './saga'
// https://github.com/zalmoxisus/redux-devtools-extension
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose
const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))
// 创建一个数据公共存储仓库;reducer真正的数据存储
const store = createStore(
  reducer,
  enhancer
)
sagaMiddleware.run(todoSagas)
export default store
// './saga'
import { takeEvery, put } from 'redux-saga/effects'
import { GET_INIT_LIST } from './actionTypes'
// import axios from 'axios'
import { initListAction } from './actionCreators'
function* getInitList() {
  try {
    const res = yield axios.get('/todolist.json')
    const action = initListAction(res.data)
    yield put(action)
  } catch(e) {
    console.log('网络请求失败', e)
  }
}
function* mySaga() {
  // 只要捕捉到GET_INIT_LIST类型,就会之后后面的getInitList方法
  yield takeEvery(GET_INIT_LIST, getInitList)
}
  
export default mySaga

评 论:

更新: 11/21/2020, 7:00:56 PM