乐趣区

关于javascript:Redux笔记

React 状态管理工具

Redux

1. Redux 外围

1.1 Redux 介绍

JavaScript 状态容器,提供可预测化的状态治理

1.2 Redux 外围概念及流程

Store: 存储状态的容器,JavaScript 对象
View: 视图,HTML 页面
Actions: 对象,形容对状态进行怎么的操作
Reducers: 函数,操作状态并返回新的状态

1.3 Redux 应用:计数器案例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux</title>
</head>
<body>
  <button id="minus">-</button>
  <span id="count">0</span>
  <button id="plus">+</button>

  <script src="./redux.min.js"></script>
  <script>

    // 3. 存储默认状态
    const 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 对象
    const store = Redux.createStore(reducer);

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

    // 5. 获取按钮 给按钮增加点击事件
    document.getElementById('minus')
    .addEventListener('click', function () {
      // 6. 获取 dispatch  触发 action 
      store.dispatch(decrement)
    })
    document.getElementById('plus')
    .addEventListener('click', function () {
      // 6. 获取 dispatch  触发 action 
      store.dispatch(increment)
    })

    // 获取 store {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}
    console.log(store)
    // 获取 store 中存储的状态 state
    console.log(store.getState());

    // 订阅数据的变动
    store.subscribe(() => {console.log(store.getState())
      document.getElementById('count').innerHTML = store.getState().count});
  </script>
</body>
</html>

1.4 Redux 外围 API

// 创立 store 对象
const store = Redux.createStore(reducer);
// 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;
  }
}
// 获取 store 中存储的状态 state
store.getState()
// 订阅数据的变动
store.subscribe(() => {console.log(store.getState())
});
// 获取 dispatch  触发 action 
store.dispatch(increment)

2. React + Redux

2.1 在 React 中不应用 Redux 时遇到的问题

在 React 中组件通信的数据流是单向的,顶层组件能够通过 Props 属性向上层组件传递数据,而上层组件不能向下层组件传递数据。要实现上层组件批改数据,须要下层组件传递批改数据的办法到上层组件。等我的项目越来越大的时候,组件间传递数据变得越来越艰难。

2.2 在 React 我的项目中退出 Redux 的益处

应用 Redux 治理数据,因为 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据艰难的问题。

2.3 下载 Redux

npm install redux react-redux

2.4 Redux 工作流程

  1. 组件通过 dispatch 办法触发 Action
  2. Store 承受 Action 并将 Action 分发给 Reducer
  3. Reducer 依据 Action 类型对状态进行更改并将更改后的状态返回给 Store
  4. 组件订阅了 Store 中的状态,Store 中的状态更新会同步到组件

2.5 Redux 应用步骤

2.5.1 创立 store
// src/store/index.js
import {createStore} from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)

在根组件中应用store

import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter'
import {Provider} from 'react-redux'
import {store} from './store'
/**
 * react-redux
 * Provider
 * connect
 */

ReactDOM.render(
  // 通过 provider 组件,将 store 放在了全局的组件能够够得着的中央
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);
2.5.2 创立 reducer
// src/store/reducers/counter.reducer.js
import {DECREMENT, INCREMENT} from "../count/counter.const";

const initialState = {count: 0}

export default 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;
  }
}
// src/store/count/counter.const.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
2.5.3 在组件中应用 connect 承受 store 外面的 state 和 dispatch

connect办法承受两个参数,返回一个高阶组件。
connect办法的第一个参数是 mapStateToProps 办法,将 store 中的 state 传递到组件的 props 中,mapStateToProps办法的参数是state,返回值是一个对象,会传递到组件中,写法如下:

const mapStateToProps = (state) => ({
  count: state.count,
  a: 'a', // 这里怎么定义,组件中就能够或失去一个属性
})

connect办法的第二个参数是 mapDispatchToProps 办法,将 store 中的 dispatch 传递到组件的 props 中,mapDispatchToProps办法的参数是dispatch,返回值是一个对象,对象中的办法能够应用dispatch, 这个对象中的办法会传递到组件中,写法如下:

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

此外,咱们还能够通过 redux 中的 bindActionCreators 来帮咱们创立 action 函数:

import {bindActionCreators} from 'redux'

// bindActionCreators 会返回一个对象
const mapDispatchToProps = dispatch => (
  // 解构
  ...bindActionCreators({increment () {return { type: 'increment'}
    },
    decrement () {return { type: 'decrement'}
    }
  }, dispatch)
)

或者写成:

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

也能够将 bindActionCreators 的第一个参数进行抽离:

import * as counterActions from '../store/actions/counter.actions'

const mapDispatchToProps = dispatch => bindActionCreators(conterActions, dispatch)
// src/store/actions/counter.actions.js
import {DECREMENT, INCREMENT} from "../count/counter.const"

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

connect办法承受 mapStateToPropsmapDispatchToProps,返回一个高阶组件,而后传入 Counter 组件进行导出:

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

最终组件代码如下:

// src/components/Counter.js
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as counterActions from '../store/actions/counter.actions'

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

// 1. connect 会帮忙咱们去订阅 store,当 store 中的状态产生了变动后,能够帮咱们从新渲染组件
// 2. connect 办法能够让咱们获取 store 中的状态,将状态通过组建的 props 属性映射给组件
// 3. connect 办法能够让咱们获取 dispatch 办法

const mapStateToProps = (state) => ({
  count: state.count,
  a: 'a', // 这里怎么定义,组件中就能够或失去一个属性
})

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
2.5.4 为 action 传递参数
  1. 传递参数

    <button onClick={() => increment(5)}> + 5</button>
  2. 承受参数,传递reducer

    export const increment = payload => ({type: INCREMENT, payload})
    export const decrement = payload => ({type: DECREMENT, payload})
  3. reducer依据承受收到的数据进行解决

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

2.6 redux 实现弹出框案例

store中的状态越多,reducer中的 switch 分支就会越多,不利于保护,须要拆分reducer

src/index.js

// src/index.js
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')
);

src/store/index.js

// src/store/index.js
import {createStore} from 'redux'
import reducer from './reducers/counter.reducer'

export const store = createStore(reducer)

src/store/reducers/counter.reducer.js

// src/store/reducers/counter.reducer.js
import {DECREMENT, INCREMENT} from "../const/counter.const";
import {HIDEMODAL, SHOWMODAL} from "../const/modal.const";

const initialState = {
  count: 0,
  show: false
}

export default function reducer (state = initialState, action) {switch (action.type) {
    case INCREMENT:
      return {...state, count: state.count + action.payload};
    case DECREMENT:
      return {...state, count: state.count - action.payload};
    case SHOWMODAL:
      return {...state, show: true};
    case HIDEMODAL:
      return {...state, show: false};
    default:
      return state;
  }
}

src/App.js

// src/App.js
import Modal from './components/Modal'
import Counter from './components/Counter'

function App() {
  return (
    <div className="App">
      <Counter />
      <Modal />
    </div>
  );
}

export default App;

src/components/Modal.js

// src/components/Modal.js
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as modalActions from '../store/actions/modal.actions'

function Modal ({showStatus, show, hide}) {
  const styles = {
    display: showStatus ? 'block': 'none',
    width: 200,
    height: 200,
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    margin: 'auto',
    backgroundColor: 'skyblue'
  }
  return (
    <div>
      <button onClick={show}> 显示 </button>
      <button onClick={hide}> 暗藏 </button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({showStatus: state.show})
const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Modal)

src/store/actions/modal.action.js

// src/store/actions/modal.action.js
import {HIDEMODAL, SHOWMODAL} from "../const/modal.const"

export const show = () => ({ type: SHOWMODAL})
export const hide = () => ({ type: HIDEMODAL})

src/store/const/modal.const.js

// src/store/const/modal.const.js
export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'

2.7 拆分reducer

应用 reducer 提供的工具 combineReducers 合并每一个小的reducer

src/store/reducers/root.reducer.js

// src/store/reducers/root.reducer.js
import {combineReducers} from 'redux'
import CounterReducer from './counter.reducer'
import ModalReducer from './modal.reducer'

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

src/store/reducers/counter.reducer.js

// src/store/reducers/counter.reducer.js
import {DECREMENT, INCREMENT} from "../const/counter.const";

const initialState = {count: 0,}

export default function counterReducer (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;
  }
}

src/store/reducers/modal.reducer.js

// src/store/reducers/modal.reducer.js
import {HIDEMODAL, SHOWMODAL} from "../const/modal.const";
const initialState = {show: false}

export default function modalReducer (state = initialState, action) {switch (action.type) {
    case SHOWMODAL:
      return {...state, show: true};
    case HIDEMODAL:
      return {...state, show: false};
    default:
      return state;
  }
}

创立 store 时传入的 reducer 则来自于咱们方才定义的root.reducer.js

import {createStore} from 'redux'
import RootReducer from './reducers/root.reducer'

export const store = createStore(RootReducer)

在每个组件中的 mapStateToProps 中也要产生相应的扭转 (state.counterstate.modal):

const mapStateToProps = (state) => ({count: state.counter.count,})
const mapStateToProps = state => ({showStatus: state.modal.show})

3. Redux 中间件

3.1 什么是中间件

中间价容许咱们扩大和加强 redux 应用程序

3.2 开发 Redux 中间件

开发中间件的模板

export default store => next => action => {}

3.3 注册中间件

中间件在开发实现当前只有被注册能力在 Redux 的工作流程中失效

src/store/index.js

// src/store/index.js
import {createStore, applyMiddleware} from 'redux'
import logger from './middlewares/logger'

createStore(reducer, applyMiddleware(logger))

src/store/middleware/logger.js

const logger = store => next => action => {console.log(store)
  console.log(action)
  next(action) // 千万别忘了调用 next(action)
}
export default logger

如果注册多个中间件,中间件的执行程序就是注册程序,如:

createStore(reducer, applyMiddleware(
  logger,
  test
))

那么执行程序就是先执行 logger 中间件,再执行 test 中间件。
如果中间件中的结尾不调用next(action),则整个流程就会卡在此处不会再往后执行了

3.4 Redux 中间件开发实例 thunk(异步中间件)

以后这个中间件函数不关怀你想执行什么样的异步操作,只关怀你执行的是不是异步操作,
如果你执行的是异步操作,你在触发 action 的时候,给我传递一个函数,如果执行的是同步操作,就传递一个 action 对象,
异步操作代码要写在你传进来的函数中
当这个中间件函数,在调用你传进来的函数时要将 dispatch 办法传递过来

src/store/middleware/thunk.js

// src/store/middleware/thunk.js
import {DECREMENT, INCREMENT} from "../const/counter.const";

const thunk = ({dispatch}) => next => action => {if (typeof action === 'function') {return action(dispatch) // action 办法外部会发动新的 dispatch 
  }
  next(action)
}
export default thunk

在 action 文件中定义异步函数 action:

src/store/actions/modal.actions.js

// src/store/actions/modal.actions.js
import {HIDEMODAL, SHOWMODAL} from "../const/modal.const"

export const show = () => ({ type: SHOWMODAL})
export const hide = () => ({ type: HIDEMODAL})

export const show_async = () => dispatch => {setTimeout(() => {dispatch(show())
  }, 2000);
}

本来应用 show 的中央,当初改用show_async, 实现了异步的性能

4. Redux 罕用中间件

4.1 redux-thunk

4.1.1 redux-thunk 下载

npm install redux-thunk

4.1.2 引入 redux-thunk

import thunk from 'redux-thunk';

4.1.3 注册 redux-thunk

import {applyMiddleware} from 'redux'
createStore(rootReducer, applyMiddleware(thunk));

4.1.4 应用 redux-thunk 中间件

const loadPosts = () => async dispatch => {const posts = await axios.get('/api/posts').then(response => response.data);
  dispatch({type: LOADPOSTSSUCCE, payload: posts});
}

4.2 redux-saga

4.2.1 redux-saga 解决的问题

redux-saga能够将异步操作从 Action Creator 文件中抽离进去,放在一个独自的文件中。

4.2.2 下载 redux-saga
npm install redux-saga
4.2.3 创立 redux-saga 中间件

src/store/index.js

// src/store/index.js
import createSagaMiddleware from 'redux-saga';
const sagaMiddleware = createSagaMiddleware();
4.2.4 注册 sagaMiddleware

src/store/index.js

// src/store/index.js
createStore(reducer, applyMiddleware(sagaMiddleware))
4.2.5 应用 saga 承受 action 异步执行操作

src/store/sagas/counter.saga.js

// src/store/sagas/counter.saga.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) // 此处会暂停 2 秒钟
  yield put(increment(action.payload))
}

export default function * counterSaga () {
  // 接管 action
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn) // 第二个函数形参会承受一个 action 函数
}

src/store/actions/counter.actions.js

// src/store/actions/counter.actions.js

// 给 saga 应用
export const increment_async = (payload) => ({type: INCREMENT_ASYNC, payload});

src/store/const/counter.const.js

// src/store/const/counter.const.js
export const INCREMENT_ASYNC = 'increment_async'

src/components/Counter.js

      <button onClick={() => increment_async(20)}>+</button>
4.2.6 启动 saga

src/store/index.js

// src/store/index.js
import counterSaga from './sagas/counter.saga'

sagaMiddleware.run(counterSaga);
4.2.7 合并 saga

src/store/saga/root.saga.js

// src/store/saga/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()])
}

modal.saga.js 没变,modal.saga.js 如下

src/store/saga/modal.saga.js

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

// takeEvery 接管 action 
// put 触发 action 

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

export default function * modalSaga () {
  // 接管 action
  yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}

store 入口文件中的 saga 中间件启动 root.saga

src/store/index.js

// src/store/index.js
import rootSaga from './sagas/root.saga'

sagaMiddleware.run(rootSaga)

4.3 redux-actions

4.3.1 redux-actions 解决的问题

redux 流程中大量的样板代码读写很苦楚,应用 redux-action 能够简化 Action 和 Reducer 的解决

4.3.2 redux-action 下载
npm install redux-actions
4.3.3 创立 Action
import {createAction} from 'redux-actions'

const increment_action = createAction('increment');
const decrement_action = createAction('decrement');
4.3.4 创立 Reducer

src/store/actions/counter.actions.js

src/store/actions/counter.actions.js
// 应用 redux-actions 
import {createAction} from 'redux-actions'

export const increment = createAction('increment')
export const decrement = createAction('decrement')

src/store/reducers/counter.reducer.js

// src/store/reducers/counter.reducer.js
import {handleActions as createReducer} from 'redux-actions'
import {increment, decrement} from '../actions/counter.actions'
const initialState = {count: 0,}
const handleIncrement = (state, action) => ({count: state.count + action.payload})

const handleDecrement = (state, action) => ({count: state.count - action.payload})

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

组件应用:
src/components/Counter.js

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

redux-actions 也能够联合在 redux-saga 中应用

MobX

1. Mobx 简介

1.1 Mobx 介绍

简略,可扩大的状态治理库

Mobx 是由 Mendix(代码开发平台), Coinbase(比特币公司), Facebook 开源和泛滥集体赞助商所资助的
React 和 Mobx 是一对强力组合,React 负责渲染利用状态,Mobx 负责管理利用状态供 React 应用

1.2 MobX 浏览器反对

MobX 5 版本运行在任何反对 ES6 Proxy 的浏览器,不反对 IE11,Node.js 6
MobX 4 能够运行在任何反对 ES5 的浏览器上
MobX 4 和 5 的 API 是雷同的

2. 开发前的筹备

2.1 启用装璜器语法反对(形式一)

  1. 弹射我的项目底层配置:npm run eject
  2. 下载装璜器语法 babel 插件:npm install @babel/plugin-proposal-decorators
  3. 在 package.json 文件中退出配置
"babel": {
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {"legacy": true}
    ]
  ]
}

启用装璜器语法反对(形式二)

  1. npm install react-app-rewired customize-cra @babel/plugin-proposal-decorators
  2. 在我的项目根目录下创立 config-overrides.js 并退出配置

    const {override, addDecoratorsLegacy} = require("customize-cra");
    module.exports = override(addDecoratorsLegacy());
  3. package.json

    "scripts": {
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test"
    }

    2.2 解决 vscode 编辑器对于装璜器语法的正告

    在 vscode 按 command + 逗号,而后在输入框中输出 javascript.implicitProjectConfig.experimentalDecorators
    批改配置:"javascript.implicitProjectConfig.experimentalDecorators": true

3. Mobx + React

3.1 下载 Mobx

npm install mobx mobx-react

3.2 Mobx 工作流程

Action -> state -> Views

5. Mobx 数据监测

5.1 computed 计算值

什么时候应用计算值
将简单的业务逻辑从模板中进行抽离

5.2 autorun 办法

当监听的状态发生变化时,你想依据状态产生“成果”,请应用 autorun.
autorun 会在初始化的时候执行一次,会在每次状态发生变化时执行。

退出移动版