React中redux的使用

326 阅读5分钟

redux的基本使用

// 创建store容器
const store = Redux.createStore(reducer)
// 创建用于处理状态的reducer函数,dispatch时也会执行
function reducer(state) {
    ...
}
// 订阅状态,当状态修改是会触发回调
store.subscribe(() => {
    ...
})
// 触发action
store.dispatch({type: 'xxx'})

安装react-redux

yarn add redux react-redux

当触发action时,会调用reducer处理函数,reducer函数中处理完毕后,就会触发订阅回调

为什么要使用Redux

  • 在React中组件通信的数据流是单向的,父组件可以通过props向子组件传递数据,子组件不能向父组件传递数据,修改父组件数据只能将修改的方法传递给子组件
  • 使用Redux管理数据,Store独立与组件,解决了组件与组件间传递数据困难的问题

在项目中使用reudx

react-redux提供了Provider组件,通过Provider组件store传递给内部组件,是内部组件可以访问store中的内容

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'

import App from './App.jsx';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
import {connect} from 'react-redux'

function App(props) {
  return (
    <div className="App">
      count:{props.count}
    </div>
  );
}
const mapStateToProps = state=>({
  count: state.count
})
export default connect(mapStateToProps)(App);

connect中的第二个参数是可以让我们获取到dispatch方法(之前说过dispatch方法会触发action,最终会执行reducer函数)

...
const mapStateToProps = state=>({
  count: state.count
})
const mapDispatchToProps = dispatch =>({
  increment(){
    return dispatch({
      type: 'increment'
    })
  },
  decrement(){
    return dispatch({
      type: 'decrement'
    })
  }
})
export default connect(mapStateToProps, mapDispatchToProps)(App);

处理reducer

import {
    createStore
} from 'redux'

const initState = {
    count: 0
}

function reducer(state = initState, action) {
    switch(action.type){
        case 'decrement':
            return {
                count: state.count - 1
            };
        case 'increment':
            return {
                count: state.count + 1
            };
         default:
            return state
    }
}

const store = createStore(reducer)

export default store

使用bindActionsCreators

import { bindActionCreators } from 'redux'
...
const mapDispatchToProps = dispatch => bindActionCreators({
  increment() {
    return {
      type: 'increment'
    }
  },
  decrement() {
    return {
      type: 'decrement'
    }
  }
}, dispatch)

mapDispatchToProps中的action提取到单独的文件中

// actions.js
export const increment = () => ({
    type: 'increment'
})

export const decrement = () => ({
    type: 'decrement'
})
import * as countActions from './store/actions'
... 
const mapDispatchToProps = dispatch => bindActionCreators(countActions, dispatch)

拆分多个reducer,当reducer中的代码比较多时,可以拆分进行管理

import {
    combineReducers
} from 'redux'
import countReducer from './count'
import showReducer from './show'

export default combineReducers({
    counter: countReducer,
    modal: showReducer
})
// count.js
const initState = {
    count: 0
}
export default function countReducer(state = initState, action) {
    switch(action.type){
        case 'decrement':
            return {
                count: state.count + 1
            };
        case 'increment':
            return {
                count: state.count + 1
            };
         default:
            return state
    }
}

在组件中使用

...
const mapStateToProps = state => ({
  count: state.counter.count,
  show: state.modal.show
})
const mapDispatchToProps = dispatch => bindActionCreators(countActions, dispatch)

传递参数

export const increment = payload => ({
    type: 'increment',
    payload
})

export const decrement = payload => ({
    type: 'decrement',
    payload
})

export default function countReducer(state = initState, action) {
    switch (action.type) {
        case 'decrement':
            return {
                count: state.count - (action.payload ? action.payload : 1)
            };
        case 'increment':
            return {
                count: state.count + (action.payload ? action.payload : 1)
            };
        default:
            return state
    }
}
<button onClick={() => props.decrement(5)}>-</button>

redux的中间件

开发一个redux中间件

export default function logger(store) {
    return next => {
        return action => {
            console.log(action)
            next(action)
        }
    }
}

引入中间件

import reducer from './reducers'
import {
    applyMiddleware
} from 'redux'
import logger from './middlewares/loggger'

const store = createStore(reducer, applyMiddleware(logger))

export default store

开发一个支持异步操作的中间件

export default function thunk(store) {
    return next => {
        return action => {
          // 如果action是个函数,就执行这个函数
          // 并把dispatch传递过去
            if (typeof action === 'function') {
                return action(store.dispatch)
            }
            next(action)
        }
    }
}

在异步action中,异步任务执行完成后,调用dispatch方法触发其他的action

export const deplay_increment = payload => dispatch => {
    setTimeout(() => {
        dispatch(increment(payload))
    }, 2000)
}
...
const store = createStore(reducer, applyMiddleware(logger, thunk))
...

redux-thunk

redux-thunk的使用方法和上面实现的thunk中间件的使用方法一样

...
import thunk from 'redux-thunk'
...
const store = createStore(reducer, applyMiddleware(logger,thunk))

redux-actions

在上面的代码中,我们创建action,写了比较多的样本代码,甚至,还需要将action.type写成常量提取到单独的文件进行管理,这样增加了代码量

通过使用redux-actions可以简化这些过程

import {
    createAction
} from 'redux-actions'

export const decrement = createAction('decrement')
export const increment = createAction('increment')
import {
    handleActions as createReducer
} from 'redux-actions'
import {
    decrement,
    increment
} from '../actions'

const countReducer = createReducer({
    [increment]: (state, action) => {
        return {
            count: state.count + (action.payload ? action.payload : 1)
        }
    },
    [decrement]: (state, action) => {
        return {
            count: state.count - (action.payload ? action.payload : 1)
        }
    }
}, initState)
export default countReducer

redux-saga

redux-saga也是用来处理异步操作的,可以将要处理的异步操作放到单独的文件中

import {
    takeEvery,
    put,
    delay
} from 'redux-saga/effects'
import {
    deplay_increment,
    increment
} from '../actions'
// takeEvery  接收action
// put 触发action

// 处理
function* handleDelayIncrement(action) {
    yield delay(2000)
    // 触发action
    yield put(increment(action.payload))
}

export default function* CounterSaga() {
  // 接收action
    yield takeEvery(deplay_increment, handleDelayIncrement)
}

引入redux-saga

import {
    createStore
} from 'redux'
import {
    applyMiddleware
} from 'redux'
import createSagaMiddleWare from 'redux-saga'
import reducer from './reducers'
import CounterSaga from './sagas/couter'

const sageMiddleWare = createSagaMiddleWare()
const store = createStore(reducer, applyMiddleware(sageMiddleWare))

sageMiddleWare.run(CounterSaga)

export default store

合并多个saga,使用redux-saga/effects中all方法可以将多个saga文件进行合并

import {
    all
} from 'redux-saga/effects'
import counterSaga from './couter'
import modalSaga from './modal'

export default function* allSaga() {
    yield all([
        counterSaga(),
        modalSaga()
    ])
}

Redux Toolkit

Redux Toolkit用来简化redux样板代码

在应用中增加toolkit

npm install @reduxjs/toolkit react-redux

新建应用

npx create-react-app --template redux

创建reducer

import {
    configureStore
} from '@reduxjs/toolkit'

export const TODOS_KEY = 'todos'

const {
    reducer: todoReducer,
    actions
} = createSlice({
    name: TODOS_KEY,
    initialState: [],
    reducers: {
        addTodo(state, actions) {
            state.push(actions.payload)
        }
    }
})

export const {
    addTodo
} = actions
export  default TodosReducer

创建store

import {
    configureStore
} from '@reduxjs/toolkit'

export const {
    addTodo
} = actions

export default configureStore({
    reducer: {
        [TODOS_KEY]: todoReducer
    },
    devTools: process.env.NODE_ENV !== 'production'
})

在组件中使用,在组件中可以使用useSelector就不用再写connect等那些样板代码了

import React from 'react';
import {useDispatch, useSelector} from 'react-redux'
import { TODOS_KEY, addTodo } from './store/todo';

function App() {
  const dispatch = useDispatch()
  const todos = useSelector(state => state[TODOS_KEY])
  return (
    <div className="App">
      <button onClick={() => dispatch(addTodo({title: '测试一下哈哈哈'}))}>add</button>
      <ul>
        {
          todos.map((todo, index) => <li key={index}>{todo.title}</li>)
        }
      </ul>
    </div>
  )
}

export default App

执行异步操作

写法一

import {
    createSlice,
    createAsyncThunk
} from '@reduxjs/toolkit'

export const getTodos = createAsyncThunk('todos/getTodos',  (payload, thunkAPI)=> {
    setTimeout(() => {
        thunkAPI.dispatch(setTodos([{title: '默认todo'}]))
    },200)
})

const {
    reducer: TodosReducer,
    actions
} = createSlice({
    name: TODOS_KEY,
    initialState: [],
    reducers: {
        addTodo(state, action) {
            state.push(action.payload)
        },
        setTodos(state, action) {
            action.payload.forEach(todo => {
                state.push(todo)
            })
        }
    }
})

写法二:

export const getTodos = createAsyncThunk('todos/getTodos', (payload, thunkAPI) => {
    return getData().then(res => res.data)
})
const {
    reducer: TodosReducer,
    actions
} = createSlice({
    name: TODOS_KEY,
    initialState: [],
    reducers: {
        addTodo(state, action) {
            state.push(action.payload)
        }
    },
    extraReducers: {
        // 三种promise的状态
        [getTodos.fulfilled]: (state, action) => {
            action.payload.forEach(todo => {
                state.push(todo)
            })
        }
    }
})

配置中间件

在toolkit中提供的configureStore方法中的middleware选项可以设置中间件

export default configureStore({
    reducer: {
        [TODOS_KEY]: TodosReducer
    },
    middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
    devTools: process.env.NODE_ENV !== 'production'
})

注意,使用时应把toolkit的默认中间件给设置进来,不让设置的中间件就会覆盖掉所有的toolkit中间件,导致程序无法运行

文档对应地址 redux-toolkit.js.org/api/getDefa…

实体适配器

import {
    ...
    createEntityAdapter
} from '@reduxjs/toolkit'
const todosAdapter = createEntityAdapter({
    selectId: (todo) => todo.id
})
let id = 0
const {
    reducer: TodosReducer,
    actions
} = createSlice({
    name: TODOS_KEY,
    initialState: todosAdapter.getInitialState(),
    reducers: {
        addTodo(state, action) {
            action.payload.id = id++
            todosAdapter.addOne(state, action.payload)
        }
    },
    extraReducers: {
        [getTodos.fulfilled]: todosAdapter.addMany
    }
})

组件中使用

function App() {
  const dispatch = useDispatch()
+  const todos = useSelector(state => state[TODOS_KEY].entities)
-  const todos = useSelector(state => state[TODOS_KEY])
  useEffect(() => {
    dispatch(getTodos())
  }, [dispatch])
  return (
    <div className="App">
      <button onClick={() => dispatch(addTodo({title:'hahah'}))}>+</button>
      <ul>
        {
+          Object.values(todos).map((todo, index) => (
-          todos.map((todo, index) => (
            <div key={index}>{todo.title}</div>
          ))
        }
      </ul>
    </div>
  )
}

状态选择器

上面代码在组件中获取状态数据变得有点复杂

const {
    selectAll
} = todosAdapter.getSelectors()
export const selectTodos = createSelector(state => state[TODOS_KEY], selectAll)
function App() {
  const dispatch = useDispatch()
  const todos = useSelector(selectTodos)
  useEffect(() => {
    dispatch(getTodos())
  }, [dispatch])
  return (
    <div className="App">
      <button onClick={() => dispatch(addTodo({title:'hahah'}))}>+</button>
      <ul>
        {
          todos.map((todo, index) => (
            <div key={index}>{todo.title}</div>
          ))
        }
      </ul>
    </div>
  )
}