Redux

目前流行的状态管理Zustand

1、Redux 核⼼

Redux 介绍
JavaScript 状态容器,提供可预测化的状态管理
在这里插入图片描述

2、Redux的工作流程

在这里插入图片描述
Store:存储状态的容器,JavaScript对象
View: 视图,HTML⻚⾯
Actions: 对象,描述对状态进⾏怎样的操作
Reducers:函数,操作状态并返回新的状态

3、小案例

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="plus">+</button>
  <span id="count">0</span>
  <button id="minus">-</button>
  <script src="redux.min.js"></script>
  <script>
    // 3. 存储默认状态
    var initialState = {
      count: 0
    }
    // 2. 创建 reducer 函数
    function reducer (state = initialState, action) {
      switch (action.type) {
        case 'increment':
          return {count: state.count + 1};
        case 'decrement':
          return {count: state.count - 1}
        default:
          return state;
      }
    }
    // 1. 创建 store 对象
    var store = Redux.createStore(reducer);

    // 4. 定义 action
    var increment = { type: 'increment' };
    var decrement = { type: 'decrement' };

    // 5. 获取按钮 给按钮添加点击事件
    document.getElementById('plus').onclick = function () {
      // 6. 触发action
      store.dispatch(increment);
    }

    document.getElementById('minus').onclick = function () {
      // 6. 触发action
      store.dispatch(decrement);
    }

    // 7. 订阅 store
    store.subscribe(() => {
      // 获取store对象中存储的状态
      // console.log(store.getState());
      document.getElementById('count').innerHTML = store.getState().count;
    })
  </script>
</body>
</html>

4、Redux核心api

在这里插入图片描述

5、React + Redux

在 React 中不使⽤ Redux 时遇到的问题

在React中组件通信的数据流是单向的, 顶层组件可以通过props属性向下层组件传递数据, ⽽下层组件不能向上层组件传递数据, 要实现下层组件修改数据, 需要上层组件传递修改数据的⽅法到下层组件. 当项⽬越来越⼤的时候, 组件之间传递数据变得越来越困难

在这里插入图片描述
在 React 项⽬中加⼊ Redux 的好处

使⽤Redux管理数据,由于Store独⽴于组件,使得数据管理独⽴于组件,解决了组件与组件之间传递数据困难的问题。
在这里插入图片描述

6、react-redux

在这里插入图片描述
在这里插入图片描述
Counter组件

import React from 'react';
import { connect } from 'react-redux';

function Counter (props) {
  return <div>
    <button onClick={() => props.dispatch({type: 'increment'})}>+</button>
    <span>{props.count}</span>
    <button onClick={() => props.dispatch({type: 'decrement'})}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.count
});


export default connect(mapStateToProps)(Counter);
  1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
  2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
  3. connect 方法可以让我们获取 dispatch 方法

优化1

view部分

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import { Provider } from 'react-redux';
const initialState = {
  count: 0
}

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

ReactDOM.render(
  // 通过provider组件 将 store 放在了全局的组件可以够的到的地方
  <Provider store={store}><App/></Provider>,
  document.getElementById('root')
);

/*
  react-redux
    Provider
    connect
*/

count组件部分

import React from 'react';
import { connect } from 'react-redux';

function Counter ({count, increment, decrement}) {
  return <div>
    <button onClick={increment}>+</button>
    <span>{count}</span>
    <button onClick={decrement}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => ({
  increment() {
    dispatch({type: 'increment'})
  },
  decrement() {
    dispatch({type: 'decrement'})
  }
})

export default connect(mapStateToProps,mapDispatchToProps)(Counter);

优化2 避免重复调dispatch

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

function Counter ({count, increment, decrement}) {
  return <div>
    <button onClick={increment}>+</button>
    <span>{count}</span>
    <button onClick={decrement}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.count
});

// 避免重复调用dispatch

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

export default connect(mapStateToProps,mapDispatchToProps)(Counter);

优化3

store/counter.action.js

export const increment = ()=> ({type: 'increment'});
export const decrement = () => ({type: 'decrement'});
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';

function Counter ({count, increment, decrement}) {
  return <div>
    <button onClick={increment}>+</button>
    <span>{count}</span>
    <button onClick={decrement}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.count
});

// 避免重复调用dispatch

const mapDispatchToProps = dispatch => (bindActionCreators(couterActions, dispatch))

export default connect(mapStateToProps,mapDispatchToProps)(Counter);

优化4

store/index.js

import { createStore } from "redux";
import reducer from './reducers/counter.reducer'

export const store = createStore(reducer)

store/reducers/reducer-counter.js

const initialState = {
  count: 0
}

export default (state = initialState, action) => {
  switch(action.type) {
    case 'increment':
      return {
        count: state.count + 1
      }
    case 'decrement':
      return {
        count: state.count - 1
      }
    default: 
      return state;
  }
}

view

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

ReactDOM.render(
  // 通过provider组件 将 store 放在了全局的组件可以够的到的地方
  <Provider store={store}><App/></Provider>,
  document.getElementById('root')
);

/*
  react-redux
    Provider
    connect
*/

优化6 加参数

reducer

import { INCREMENT, DECREMENT } from "../const/counter.const";

const initialState = {
  count: 0
}

export default (state = initialState, action) => {
  switch(action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      }
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      }
    default: 
      return state;
  }
}

action

import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";

export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});

export const increment_async = payload => ({type: INCREMENT_ASYNC, payload});

组件应用

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';

function Counter ({count, increment, decrement}) {
  return <div>
    <button onClick={() => increment(5)}>+</button>
    <span>{count}</span>
    <button onClick={() => decrement(5)}>-</button>
  </div>
}

// 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件
// 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = state => ({
  count: state.count
});

// 避免重复调用dispatch

const mapDispatchToProps = dispatch => (bindActionCreators(couterActions, dispatch))

export default connect(mapStateToProps,mapDispatchToProps)(Counter);

7、拆分合并reducer

combineReducers中间件

count-reducer.js

import { INCREMENT, DECREMENT } from "../const/counter.const";

const initialState = {
  count: 0
}

export default (state = initialState, action) => {
  switch(action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      }
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      }
    default: 
      return state;
  }
}

root-reducer.js

import { combineReducers } from 'redux';
import CounterReducer from './counter.reducer';
import ModalReducer from './modal.reducer';

// { counter: { count: 0 }, model: { show: false } }
export default combineReducers({
  counter: CounterReducer,
  modal: ModalReducer
})

store/index.js

import { createStore } from "redux";
import rootReducer from './reducers/root.reducer'

export const store = createStore(rootReducer)

8、中间件概念介绍

什么是中间件?

中间件允许我们扩展redux应⽤程序。

在这里插入图片描述

9、开发redux中间件

开发中间件的模板代码

在这里插入图片描述
注册中间件

中间件在开发完成以后只有被注册才能在Redux的⼯作流程中⽣效

在这里插入图片描述
多个中间件

import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import logger from "./middleware/logger";
import test from "./middleware/test";
import thunk from './middleware/thunk';


export const store = createStore(RootReducer, applyMiddleware(thunk, logger, test));


帮助理解

在这里插入图片描述
logger.js 用于测试

export default store => next => action => {
  console.log(store);
  console.log(action);
  next(action);
}

2秒之后执行

export default store => next => action => {
 if(actiob.type === 'increment' || action.type === 'decrement') {
   setTimeout(() => {
	  next(action);
}, 2000)
}
}

优化

export default ({dispatch}) => next => action => {
  // 1. 当前这个中间件函数不关心你想执行什么样的异步操作 只关心你执行的是不是异步操作
  // 2. 如果你执行的是异步操作 你在触发action的时候 给我传递一个函数 如果执行的是同步操作 就传递action对象
  // 3. 异步操作代码要写在你传递进来的函数中
  // 4. 当前这个中间件函数在调用你传递进来的函数时 要将dispatch方法传递过去
  if (typeof action === 'function') {
    return action(dispatch)
  }
  next(action)
}

action.js

export const increment_async = payload => dispatch => {
  setTimeout(() => {
    dispatch(increment(payload))
  },2000)
}

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as couterActions from '../store/actions/counter.actions';

function Counter ({count, increment, decrement, increment_async}) {
  return <div>
    <button onClick={() => increment_async(20)}>+</button>
    <span>{count}</span>
    <button onClick={() => decrement(5)}>-</button>
  </div>
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

10、Redux常用中间件

执行异步操作

1、redux-thunk

npm install redux-thunk

在这里插入图片描述

import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import thunk from 'redux-thunk';

export const store = createStore(RootReducer, applyMiddleware(thunk));


上面的案例依然好用

11、redux-saga

redux-saga和redux-thunk功能一样处理异步代码

redux-saga 可以将异步操作从 Action Creator ⽂件中抽离出来,放在⼀个单独的⽂件中

npm install redux-saga

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
store/index.js

import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import createSagaMidddleware from 'redux-saga';
import rootSaga from './sagas/root.saga';

// 创建 sagaMiddleware
const sagaMiddleware = createSagaMidddleware();

export const store = createStore(RootReducer, applyMiddleware(sagaMiddleware));

// 启动 counterSaga
sagaMiddleware.run(rootSaga)

counter.action.js

import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";

export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});

export const increment_async = payload => ({type: INCREMENT_ASYNC, payload});

store/saga/counter.js

import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter.actions';
import { INCREMENT_ASYNC } from '../const/counter.const';

// takeEvery 接收 action
// put 触发 action

function* increment_async_fn (action) {
  yield delay(2000);
  yield put(increment(action.payload))
}

export default function* counterSaga () {
  // 接收action
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}

12、saga文件的拆分与合并

没有拆分的情况下

import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter.actions';
import { INCREMENT_ASYNC } from '../const/counter.const';
import { SHOWMODAL_ASYNC } from '../const/modal.const';
import { show } from '../actions/modal.actions';


// takeEvery 接收 action
// put 触发 action

function* increment_async_fn (action) {
  yield delay(2000);
  yield put(increment(action.payload))
}

export default function* counterSaga () {
  // 接收action
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}

function* showModal_async_fn () {
  yield delay(2000);
  yield put(show());
}

export default function* modalSaga () {
  yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}

saga拆分

modal.saga.js

import { takeEvery, put, delay } from 'redux-saga/effects';
import { SHOWMODAL_ASYNC } from '../const/modal.const';
import { show } from '../actions/modal.actions';

function* showModal_async_fn () {
  yield delay(2000);
  yield put(show());
}

export default function* modalSaga () {
  yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}

root.saga.js

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

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

store/index.js

import { createStore, applyMiddleware } from "redux";
import RootReducer from "./reducers/root.reducer";
import createSagaMidddleware from 'redux-saga';
import rootSaga from './sagas/root.saga';

// 创建 sagaMiddleware
const sagaMiddleware = createSagaMidddleware();

export const store = createStore(RootReducer, applyMiddleware(sagaMiddleware));

// 启动 counterSaga
sagaMiddleware.run(rootSaga)

13、redux-action的使用

redux流程中⼤量的样板代码读写很痛苦, 使⽤redux-actions可以简化Action和Reducer的处理
npm i redux-actions

在这里插入图片描述在这里插入图片描述

actions原来的代码

import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const";
export const increment = payload => ({type: INCREMENT, payload});
export const decrement = payload => ({type: DECREMENT, payload});
export const increment_async = payload => ({type: INCREMENT_ASYNC, payload});

使用redux-actions之后

在这里插入图片描述
reducer里面原来的代码

import { INCREMENT, DECREMENT } from "../const/counter.const";

const initialState = {
  count: 0
}

export default (state = initialState, action) => {
  switch(action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      }
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      }
    default: 
      return state;
  }
}

使用redux-actions之后

import { handleActions as createReducer } from "redux-actions";
import { increment, decrement } from "../actions/counter.actions";

const initialState = {
  count: 0
}

function handleIncrement(state,action) {
  return {
    count: state.count + action.payLoad
  }
}

function handleDecrement(state,action) {
  return {
    count: state.count - action.payLoad
  }
}

export default createReducer({
  [increment]: handleIncrement,
  [decrement]: handleDecrement
}, initialState)

14、shopping案例

npm i redux react-redux redux-saga redux-actions

看代码

15、redux源码实现

middleware/thunk.js

function thunk (store) {
  return function (next) {
    return function (action) {
      console.log('thunk');
      next(action)
    }
  }
}

middleware/logger.js

function logger (store) {
  return function (next) {
    return function (action) {
      console.log('logger');
      next(action)
    }
  }
}

myRedux.js

function createStore (reducer, preloadedState, enhancer) {
  // reducer 类型判断 
  if (typeof reducer !== 'function') throw new Error('redcuer必须是函数');

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('enhancer必须是函数')
    }
    return enhancer(createStore)(reducer, preloadedState);
  }
  // 状态
  var currentState = preloadedState;
  // 订阅者
  var currentListeners = [];
  // 获取状态
  function getState () {
    return currentState;
  }
  // 用于触发action的方法
  function dispatch (action) {
    // 判断action是否是一个对象
    if (!isPlainObject(action)) throw new Error('action必须是一个对象');
    // 判断action中的type属性是否存在
    if (typeof action.type === 'undefined') throw new Error('action对象中必须有type属性');
    // 调用reducer函数 处理状态
    currentState = reducer(currentState, action);
    // 调用订阅者 通知订阅者状态发生了改变
    for (var i = 0; i < currentListeners.length; i++) {
      var listener = currentListeners[i];
      listener();
    }
  }
  // 订阅状态的改变
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  // 默认调用一次dispatch方法 存储初始状态(通过reducer函数传递的默认状态)
  dispatch({type: 'initAction'})

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象类型
// 判断对象的当前原型对象是否和顶层原型对象相同
function isPlainObject (obj) {
  if (typeof obj !== 'object' || obj === null) return false;
  var proto = obj;
  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto)  // 获取最顶层原型对象
  }
  return Object.getPrototypeOf(obj) === proto;
}

function applyMiddleware (...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      // 创建 store
      var store = createStore(reducer, preloadedState);
      // 阉割版的 store
      var middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch
      }
      // 调用中间件的第一层函数 传递阉割版的store对象
      var chain = middlewares.map(middleware => middleware(middlewareAPI));
      var dispatch = compose(...chain)(store.dispatch);
      return {
        ...store,
        dispatch
      }
    }
  }
}

function compose () {
  var funcs = [...arguments];
  return function (dispatch) {
    for (var i = funcs.length - 1; i >= 0; i--) {
      dispatch = funcs[i](dispatch);
    }
    return dispatch;
  }
}

function bindActionCreators (actionCreators, dispatch) {
  var boundActionCreators = {};
  for (var key in actionCreators) {
    (function (key) {
      boundActionCreators[key] = function () {
        dispatch(actionCreators[key]())
      }
    })(key)
  }
  return boundActionCreators;
}

function combineReducers (reducers) {
  // 1. 检查reducer类型 它必须是函数
  var reducerKeys = Object.keys(reducers);
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];
    if (typeof reducers[key] !== 'function') throw new Error('reducer必须是函数');
  }
  // 2. 调用一个一个的小的reducer 将每一个小的reducer中返回的状态存储在一个新的大的对象中
  return function (state, action) {
    var nextState = {};
    for (var i = 0; i < reducerKeys.length; i++) {
      var key = reducerKeys[i];
      var reducer = reducers[key];
      var previousStateForKey = state[key];
      nextState[key] = reducer(previousStateForKey, action)
    }
    return nextState;
  }
}

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="increment">+1</button>
    <span id="box">0</span>
    <button id="decrement">-1</button>
    <script src="./myRedux.js"></script>
    <script src="middlewares/logger.js"></script>
    <script src="middlewares/thunk.js"></script>
    <script>
      function counterReducer(state, action) {
        switch (action.type) {
          case "increment":
            return state + 1;
          case "decrement":
            return state - 1;
          default:
            return state;
        }
      }

      function enhancer (createStore) {
        return function (reducer, preloadedState) {
          var store = createStore(reducer, preloadedState);
          var dispatch = store.dispatch;
          function _dispatch (action) {
            if (typeof action === 'function') {
              return action(dispatch)
            }
            dispatch(action);
          }
          return {
            ...store,
            dispatch: _dispatch
          }
        }
      }
      
      var rootReducer = combineReducers({counter: counterReducer})
      
      var store = createStore(rootReducer, {counter: 100}, applyMiddleware(logger, thunk));

      store.subscribe(function () {
        document.getElementById("box").innerHTML = store.getState().counter;
      });

      var actions = bindActionCreators({increment, decrement}, store.dispatch);

      function increment () {
        return {type: "increment"}
      }

      function decrement () {
        return {type: "decrement"};
      }
      
      document.getElementById("increment").onclick = function () {
        // logger -> thunk -> reducer
        // store.dispatch({ type: "increment" });
        actions.increment()
      };

      document.getElementById("decrement").onclick = function () {
        // store.dispatch({ type: "decrement" });
        actions.decrement()
      };

    </script>
  </body>
</html>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值