深入学习redux

ReduxJavaScript状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览。ReduxFlux演变而来,避开了 Flux 的复杂性。 不管你有没有使用过它们,只需几分钟就能上手Redux

Redux

Redux Flow redux 工作流
Redux Flow
注: react组件要改变store里面的数据,首先要dispatch(派发)一个action,传递给store,然后store再把之前的state数据和action转发给reducer函数,reducer接收到action和state之后,做一些处理,然后返回一个新的state(newState)给store, 之后store用新的state替换掉之前旧的state数据,store数据改变后, react组件感知到store数据改变,然后从store重新取数据,更新组件的内容,页面发生变化.

  • 安装 Redux npm i redux
  • 创建 state容器
  • 创建 reducer 必须一个函数
  • 在state中引入reducer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    // store/index.js
    import { createStore } from 'redux'
    import reducer from './reducer'
    const store = createStore(reducer)
    export default store

    // store/reducer.js
    const defaultState = {
    inputValue: '',
    list: []
    }
    export default (state = defaultState, action) => {
    return state
    }

    // 创建action todolist.js
    // input绑定 onChange={this.InpuChange.bind(this)}
    InpuChange (e) {
    const action = { // 定义action
    type: 'change_input_value',
    value: e.target.value
    }
    store.dispatch(action) // 派发action给reducer
    }

    // store/reducer.js reducer接收action
    export default (state = defaultState, action) => {
    console.log(state, action);
    if (action.type == 'change_input_value') {
    // 拷贝一份state reducer可以接收state, 但是建议不要直接修改state
    const newState = JSON.parse(JSON.stringify(state))
    newState.inputValue = action.value
    return newState //返回给store
    }
    return state
    }
    // store进行数据替换 store中的state数据变化
    // todolist.js 更新组件页面数据
    store.subscribe(this.changeStore.bind(this)) // 订阅store的改变
    changeStore () {
    this.setState(store.getState()) // 重新获取store中state数据
    }
  • 在chrome应用商店 搜索并安装 Redux DevTools Chrome 调试Redux

    1
    2
    3
    4
    5
    // 在store.index.js 中创建store时加入这句
    const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    )
  • 添加actionTypes.js的意义在于 用常量去表示action类型, 在dispacth和reducer中action.type不一致导致字符串不报错难以排查问题, 使用常量如果写错会报错, 且便于修改action的名称,只需要修改定时的action值即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // actionTypes.js
    export const CHANGE_INPUT_VALUE = 'change_input_value'
    export const ADD_TODO_ITEM = 'add_todo_item'
    export const DELETE_TODO_ITEM = 'delete_todo_item'

    // reducer.js
    import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
    export default (state = defaultState, action) => {
    // store 和 input的value实现响应 reducer可以接收state,
    if (action.type === CHANGE_INPUT_VALUE) {
    // 拷贝一份state 一定不能直接修改state 需要返回一个新的state数据
    const newState = JSON.parse(JSON.stringify(state))
    newState.inputValue = action.value
    return newState
    }
    }

    // todoList.js
    import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
    InpuChange (e) {
    const action = {
    type: CHANGE_INPUT_VALUE,
    value: e.target.value
    }
    store.dispatch(action)
    }
  • 使用actionCreator统一创建action

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // actionCreators.js
    import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
    export const getChangeInputValue = (value) => ({
    type: CHANGE_INPUT_VALUE,
    value
    })
    export const getAddTodoItem = () => ({
    type: ADD_TODO_ITEM
    })
    export const getDeleteItem = (index) => ({
    type: DELETE_TODO_ITEM,
    index
    })
    // TodoList.js
    import { getChangeInputValue, getAddTodoItem, getDeleteItem } from './store/actionCreators'
    InpuChange(e) {
    store.dispatch(getChangeInputValue(e.target.value))
    }
    buttonClick() {
    store.dispatch(getAddTodoItem())
    }
    itemDeleteHandle(index) {
    store.dispatch(getDeleteItem(index))
    }
  • combineReducers对reducer进行拆分管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // /common/header/store/reducer.js
    const defaultState = {
    focused: false
    }
    export default (state = defaultState, action ) => {
    let newState = {...state} // 拷贝新对象
    if (action.type === 'focus_input') {
    newState.focused = true
    }
    if (action.type === 'blur_input') {
    newState.focused = false
    }
    return newState
    }
    // /store/reducer.js
    import { combineReducers } from 'redux'
    import headerReducer from '../common/header/store/reducer'
    const reducer = combineReducers({
    header: headerReducer
    })
    export default reducer
    // header/index.js
    const mapStateToProps = (state) => {
    return {
    focused: state.header.focused
    }
    }
  • 使用Immutable.js来管理store中的state数据使其不可更改
    npm i immutable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // header/reducer.js
    import * as actionCreators from './actionTypes'
    import { fromJS } from 'immutable'
    const defaultState = fromJS({
    focused: false
    })
    export default (state = defaultState, action ) => {
    if (action.type === actionCreators.SEARCH_FOCUS) {
    // immutable对象色set方法,返回一个全新的对象
    return state.set('focused',true)
    }
    if (action.type === actionCreators.SEARCH_BLUR) {
    return state.set('focused', false)
    }
    return state
    }
    // heade/index.js
    const mapStateToProps = (state) => {
    return {
    // 使用get()获取immutable对象下的state属性
    focused: state.header.get('focused')
    }
    }
  • 使用redux-immutable统一数据格式
    npm i redux-immutable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // /store/reducer.js
    // import { combineReducers } from 'redux'
    import { combineReducers } from 'redux-immutable'
    import headerReducer from '../common/header/store/reducer'
    const reducer = combineReducers({
    header: headerReducer
    })
    export default reducer
    // heade/index.js
    const mapStateToProps = (state) => {
    return {
    // 使用get()获取immutable对象下的state属性
    // focused: state.get('header').get('focused')
    focused: state.getIn(['header', 'focused']
    }
    }
  • redux的三要素 store是唯一的 只有store能够改变自己的内容 Reducer必须是纯函数

  • 纯函数指的是给固定的输入,就一定会有固定的输出,不管调用多少次,而且不会有任何的副作用

使用Redux-thunk中间件实现ajax请求(实际上是对dispatch的升级)

  • npm i redux-thunk -S
  • 在创建redux时,配置redux-thunk,redux-devtools

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { createStore, applyMiddleware, compose } from 'redux'
    import thunk from 'redux-thunk'
    import reducer from './reducer'
    const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose
    const enhancer = composeEnhancers(
    applyMiddleware(thunk)
    )
    const store = createStore(reducer, enhancer)
    export default store
  • actionCreator创建action,就可以使用函数作为返回值了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // actionCreator.js
    export const initTodoList = (data) => ({
    type: INIT_TODOLIST,
    data
    })
    export const getTodoList = () => {
    // 接收 dispatch 参数,直接使用,相当于调用store.dispatch方法
    return (dispatch) => {
    axios.get('/list.json').then( res => {
    console.log(res.data)
    dispatch(initTodoList(res.data))
    })
    }
    }
  • 在组件componentDidMount钩子函数中调用getTodoList

    1
    2
    3
    componentDidMount () {
    store.dispatch(getTodoList())
    }
  • reducer接收store转发的state和action,处理改变state

    1
    2
    3
    4
    5
    6
    7
    8
    // reducer.js
    export default (state = defaultState, action) => {
    if (action.type === INIT_TODOLIST) {
    const newState = JSON.parse(JSON.stringify(state))
    newState.list = action.data
    return newState
    }
    }

使用Redux-saga中间件处理异步请求

  • npm i redux-saga -S
  • 在创建redux时,配置redux-saga,redux-devtools

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // index.js  redux
    import { createStore, applyMiddleware, compose } from 'redux';
    import reducer from './reducer';
    import createSagaMiddleware from 'redux-saga'
    import todoSaga from './todoSaga' // saga文件处理异步请求 必须是一个generator函数
    const sagaMiddleware = createSagaMiddleware()
    const composeEnhancers =
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
    const enhancer = composeEnhancers(
    applyMiddleware(sagaMiddleware)
    );
    const store = createStore(reducer, enhancer);
    sagaMiddleware.run(todoSaga) // 执行todosaga
    export default store;
  • actionCreator创建action 在actionTypes创建常量GET_INIT_LIST

    1
    2
    3
    4
    5
    6
    // actionTypes.js
    export const GET_INIT_LIST = 'get_init_list'
    // actionCreator.js
    export const getInitList = () => ({
    type: GET_INIT_LIST
    })
  • 创建todoSaga.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import { takeEvery, put } from 'redux-saga/effects'
    import { GET_INIT_LIST } from './actionTypes'
    import axios from 'axios'
    import { initTodoList } from './actionCreators'
    function* fetchList () {
    try {
    const res = yield axios.get('/list.json')
    yield put(initTodoList(res.data))
    } catch (err) {
    consoloe.log(err)
    }
    }
    function* todoSaga () {
    yield takeEvery(GET_INIT_LIST, fetchList)
    }
    export default todoSaga
  • 在组件componentDidMount钩子函数中调用getInitList

    1
    2
    3
    4
    import { getInitList } from './store/actionCreators'
    componentDidMount () {
    store.dispatch(getInitList())
    }
  • reducer接收store转发的state和action,处理改变state

    1
    2
    3
    4
    5
    6
    7
    8
    // reducer.js
    export default (state = defaultState, action) => {
    if (action.type === INIT_TODOLIST) {
    const newState = JSON.parse(JSON.stringify(state))
    newState.list = action.data
    return newState
    }
    }

React-redux的作用是为了在项目中使用Redux更加方便。

  • npm install --save react-redux
  • 使用 Provider 提供器包裹组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import store from './store'
    import { Provider } from 'react-redux'
    import Todo from './Todo'
    const App = (
    <Provider store={store}>
    <Todo />
    </Provider>
    )
    ReactDOM.render(App, document.getElementById('root'));
  • 使用 connect 将组件和store连接起来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // Todo.js
    import React from 'react'
    import { connect } from 'react-redux'
    class Todo extends React.Component {
    render () {
    return (
    <div>
    <div>
    <input value={this.props.inputValue} onChange={this.props.changeInputValue}/>
    <button>提交</button>
    </div>
    </div>
    )
    }
    }
    const mapStateToProps = (state) => {
    return { inputValue: state.inputValue }
    }
    const mapDispatchToProps = (dispatch) => {
    return {
    changeInputValue (e) {
    dispatch({
    type: 'change_input_value',
    value: e.target.value
    })
    }
    }
    }
    export default connect(mapStateToProps, mapDispatchToProps)(Todo)
  • reducer接收store转发的state和action,处理改变state

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // reducer.js
    const defaultState = {
    inputValue: ''
    }
    export default (state = defaultState, action) => {
    if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state))
    newState.inputValue = action.value
    return newState
    }
    }
-------------本文结束感谢您的阅读-------------
0%