当前位置:   article > 正文

React 之 Redux - 状态管理_react状态管理redux

react状态管理redux

一、前言

1. 纯函数

函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念
  • 确定的输入,一定会产生确定的输出

  • 函数在执行过程中,不能产生副作用

2. 副作用

表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,
比如修改了全局变量,修改参数或者改变外部的存储
  • 纯函数在执行的过程中就是不能产生这样的副作用

  • 副作用往往是产生bug的 “温床”

3. 纯函数的案例

对数组操作的两个函数:
slice就是一个纯函数,不会修改数组本身,而splice函数不是一个纯函数
  • slice:slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组

  • splice:splice截取数组, 会返回一个新的数组, 也会对原数组进行修改

4. 判断是否纯函数

5. 纯函数的作用和优势

纯函数在函数式编程中非常重要 :
  • 可以安心的编写和安心的使用

  • 在写的时候保证了函数的纯度,只是单纯实现业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改

  • 在用的时候,确定的输入内容不会被任意篡改,并且确定的输入,一定会有确定的输出

6. 在React中

React中就要求无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改

7. 为啥使用Redux

二、Redux的核心理念

1. 核心理念 - store

store : 用于存储共享数据的仓库

2. 核心理念 - action

action : store中所有数据的变化,必须通过派发(dispatch)action来更新
action是一个普通的JavaScript对象,用来描述这次更新的type和content

3. 核心理念 - reducer

reducer : reducer是一个纯函数,将state和action联系在一起
reducer做的事情就是将传入的state和action结合起来生成一个新的state

4. Redux的三大原则

单一数据源

  • 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store

  • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护

  • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改

State是只读的

  • 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State

  • 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state

  • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题

使用纯函数来执行修改

  • 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State

  • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分

  • 但是所有的reducer都应该是纯函数,不能产生任何的副作用

三、Redux的基本使用

1. 测试项目搭建

01. 创建一个新的项目文件夹: learn-redux

02. 进入文件夹后进行项目初始化
npm init -y

03. 安装redux
npm install redux

04. 创建src目录,以及其中的index.js文件

05. 修改package.json可以执行index.js
也可不修改,直接控制台输入 node src/index.js
  1. "scripts": {
  2. // node v13.2.0之后,添加属性:"type": "module",就可以在node中对ES6模块化的支持
  3.   "type": "module",
  4. "start": "node src/index.js"
  5. },

06. 在src中创建目录store,并创建index.js
  1. // 需要配置下type,否则的话直接使用commonjs也是可以的
  2. import { createStore } from 'redux';
  3. // 1. 定义初始化数据
  4. const initialState = {
  5. name: 'coder',
  6. age: 18,
  7. counter: 100
  8. };
  9. // 2. 定义reducer纯函数
  10. // 2.1 reducer函数的第一个参数是state,第二个参数是action
  11. // 2.2 reducer函数必须返回一个新的state,这个state会覆盖原来的state
  12. // 2.3 reducer函数中,不能修改原来的state,必须返回一个新的state
  13. // 第一次调用reducer函数的时候,state是undefined,所以给state设置默认值,只会在第一次调用的时候生效
  14. const reducer = (state = initialState, action) => {
  15. // 2.4 在这里,根据action的type,来决定如何修改state,返回一个新的state
  16. switch (action.type) {
  17.     case 'xxx' : return {...state,...}
  18.   }
  19.   // 2.5 没有数据更新的时候,返回一个新的state
  20. return state;
  21. };
  22. // 3. 创建store
  23. export const store = createStore(reducer);

2. 使用store中的数据

  1. // 1. 导入store
  2. import { store } from './store/index.js';
  3. // 2. 拿到store中存储的数据
  4. console.log(store.getState()); // { name: 'coder', age: 18, counter: 100 }

3. 修改store中的数据

执行代码
  1. // 导入store
  2. import { store } from './store/index.js';
  3. // 创建一个action对象
  4. const nameAction = { type: 'change_name', name: 'star' };
  5. // 进行派发, 派发的时候,会调用reducer函数,传入state和action
  6. store.dispatch(nameAction)
  7. // 创建一个action对象
  8. const counterAction = { type: 'increment_counter', number: 10 };
  9. // 进行派发
  10. store.dispatch(counterAction);
  11. console.log(store.getState()); // { name: 'star', age: 18, counter: 110 }
在reducer函数中新增判断
  1. const reducer = (state = initialState, action) => {
  2. // action => { type: 'change_name', name: 'star' }
  3. switch (action.type) {
  4. case 'change_name':
  5. return {
  6. // 先对原来staet进行解构
  7. ...state,
  8. // 再对name进行覆盖
  9. name: action.name
  10. };
  11. case 'increment_counter':
  12. return { ...state, counter: state.counter + action.number };
  13. default:
  14. // 如果没有匹配到action.type,就返回原来的state
  15. return state;
  16. }
  17. };

4. 订阅store中的数

  1. // 导入store
  2. import { store } from './store/index.js';
  3. // 监听state的变化, 只要state发生了变化,就会调用回调函数
  4. // 返回值是一个函数,调用这个函数,就可以取消监听
  5. const unSubscribe = store.subscribe(() => {
  6. console.log('state发生了变化');
  7. console.log(store.getState());
  8. });
  9. // 派发action => 可以监听到
  10. store.dispatch({ type: 'change_name', name: 'star' });
  11. // 取消监听
  12. unSubscribe();
  13. // 派发action => 不会监听到,但是state会发生变化,因为store.dispatch会调用reducer函数
  14. store.dispatch({ type: 'increment_counter', number: 10 });

5. 优化

动态生成action

  1. // 导入store
  2. import { store } from './store/index.js';
  3. store.subscribe(() => {
  4. console.log('state发生了变化', store.getState());
  5. });
  6. // 动态生成action : actionCreator => 创建action的函数, 返回一个action对象
  7. const changeNameAction = (name) => ({ type: 'change_name', name });
  8. // store.dispatch({ type: 'change_name', name: 'star' });
  9. // store.dispatch({ type: 'change_name', name: 'coderstar' });
  10. store.dispatch(changeNameAction('star'));
  11. store.dispatch(changeNameAction('coderstar'));

目录结构优化

如果将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护
对代码进行拆分,将store、reducer、action、constants拆分成一个个文件

创建store/index.js文件
  1. import { createStore } from 'redux';
  2. import reducer from './reducer.js';
  3. // 创建store
  4. export const store = createStore(reducer);
创建store/reducer.js文件
  1. import { CHANGE_NAME, INCREMENT_COUNTER } from './constants.js';
  2. // 1. 定义初始化数据
  3. const initialState = {
  4. name: 'coder',
  5. age: 18,
  6. counter: 100
  7. };
  8. // 2. 定义reducer
  9. export const reducer = (state = initialState, action) => {
  10. switch (action.type) {
  11. case CHANGE_NAME:
  12. return {
  13. // 先对原来staet进行解构
  14. ...state,
  15. // 再对name进行覆盖
  16. name: action.name
  17. };
  18. case INCREMENT_COUNTER:
  19. return { ...state, counter: state.counter + action.number };
  20. default:
  21. // 如果没有匹配到action.type,就返回原来的state
  22. return state;
  23. }
  24. };
创建store/actionCreators.js文件 => 用于生成action
  1. // Description: Action Creators
  2. import { CHANGE_NAME, INCREMENT_COUNTER } from './constants.js';
  3. // 修改name名字的action
  4. export const changeNameAction = (name) => ({ type: CHANGE_NAME, name });
  5. // 修改counter的action
  6. export const incrementCounterAction = (number) => ({ type: INCREMENT_COUNTER, number });
创建store/constants.js文件 => 用于定义type常量
  1. // Desc: constants for actions
  2. export const CHANGE_NAME = 'change_name';
  3. export const INCREMENT_COUNTER = 'increment_counter';

6. Redux使用流程图

四、React结合Redux

1. 安装

npm install redux

2. 基本使用

 store文件夹

index.js

  1. import { createStore } from 'redux';
  2. import { reducer } from './reducer';
  3. export const store = createStore(reducer);

 constants.js

  1. export const CHANGE_COUNTER = 'change_counter';
  2. export const CHANGE_BANNER = 'change_banner';

reducer.js

  1. import { CHANGE_BANNER, CHANGE_COUNTER } from './constants';
  2. const initialState = {
  3. counter: 20,
  4. bannerList: []
  5. };
  6. export const reducer = (state = initialState, action) => {
  7. switch (action.type) {
  8. case CHANGE_COUNTER:
  9. return { ...state, counter: state.counter + action.counter };
  10. case CHANGE_BANNER:
  11. return { ...state, bannerList: [...state.bannerList, ...action.bannerList] };
  12. default:
  13. return state;
  14. }
  15. };

actionCreatores.js

  1. import { CHANGE_COUNTER, CHANGE_BANNER } from './constants';
  2. // 修改counter的action
  3. export const changeCounterAction = (counter) => ({ type: CHANGE_COUNTER, counter });
  4. // 修改bannerList的action
  5. export const changeBannerAction = (bannerList) => ({ type: CHANGE_BANNER, bannerList });

page文件夹

Home.jsx

  1. import React, { PureComponent } from 'react';
  2. import { store } from '../store';
  3. import { changeCounterAction } from '../store/actionCreatores.js';
  4. export class Home extends PureComponent {
  5. constructor(porps) {
  6. super(porps);
  7. this.state = {
  8. // 从store中获取counter的值,赋予初始化值
  9. counter: store.getState().counter
  10. };
  11. }
  12. componentDidMount() {
  13. // 监听store的变化
  14. store.subscribe(() => {
  15. this.setState({
  16. counter: store.getState().counter
  17. });
  18. });
  19. }
  20. changeCounter(num) {
  21. // 改变store中的值,通过dispatch派发action
  22. store.dispatch(changeCounterAction(num));
  23. }
  24. render() {
  25. const { counter } = this.state;
  26. return (
  27. <>
  28. <h2>Home counter : {counter}</h2>
  29. <button onClick={(e) => this.changeCounter(5)}>+5</button>
  30. {' '}
  31. <button onClick={(e) => this.changeCounter(10)}>+10</button>
  32. </>
  33. );
  34. }
  35. }
  36. export default Home;

Profily.jsx

  1. import React, { PureComponent } from 'react';
  2. import { store } from '../store';
  3. import { changeCounterAction } from '../store/actionCreatores.js';
  4. export class Profily extends PureComponent {
  5. constructor(porps) {
  6. super(porps);
  7. this.state = {
  8. // 从store中获取counter的值,赋予初始化值
  9. counter: store.getState().counter
  10. };
  11. }
  12. componentDidMount() {
  13. // 监听store的变化
  14. store.subscribe(() => {
  15. this.setState({
  16. counter: store.getState().counter
  17. });
  18. });
  19. }
  20. changeCounter(num) {
  21. // 改变store中的值,通过dispatch派发action
  22. store.dispatch(changeCounterAction(num));
  23. }
  24. render() {
  25. const { counter } = this.state;
  26. return (
  27. <>
  28. <h2>Profily counter : {counter}</h2>
  29. <button onClick={(e) => this.changeCounter(-5)}>-5</button>
  30. {' '}
  31. <button onClick={(e) => this.changeCounter(-10)}>-10</button>
  32. </>
  33. );
  34. }
  35. }
  36. export default Profily;

App.jsx

  1. import React, { PureComponent } from 'react';
  2. import Home from './page/Home';
  3. import Profily from './page/Profily';
  4. import { store } from './store';
  5. export class App extends PureComponent {
  6. constructor(porps) {
  7. super(porps);
  8. this.state = {
  9. // 从store中获取counter的值,赋予初始化值
  10. counter: store.getState().counter
  11. };
  12. }
  13. componentDidMount() {
  14. // 监听store的变化
  15. store.subscribe(() => {
  16. this.setState({
  17. counter: store.getState().counter
  18. });
  19. });
  20. }
  21. render() {
  22. const { counter } = this.state;
  23. return (
  24. <div style={{ textAlign: 'center', marginTop: '100px' }}>
  25. <h2>App counter : {counter}</h2>
  26. <hr />
  27. <hr />
  28. <hr />
  29. <Home />
  30. <hr />
  31. <hr />
  32. <hr />
  33. <Profily />
  34. </div>
  35. );
  36. }
  37. }
  38. export default App;

3. react-redux的使用

  • redux和react没有直接的关系,完全可以在React, Angular, Ember, jQuery, or vanilla 
    JavaScript中使用Redux

  • redux依然是和React库结合的更好

  • redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效

将组件和store连接

安装 => npm install react-redux

1. 修改index.js 

  1. import React from 'react';
  2. import ReactDOM from 'react-dom/client';
  3. import App from './App.jsx';
  4. // 引入Provider组件, 用于给整个项目提供一个公共的store
  5. import { Provider } from 'react-redux';
  6. import { store } from './store';
  7. const root = ReactDOM.createRoot(document.querySelector('#root'));
  8. root.render(
  9. <React.StrictMode>
  10. {/* 给整个项目提供一个公共的store */}
  11. <Provider store={store}>
  12. <App />
  13. </Provider>
  14. </React.StrictMode>
  15. );

2. 组件中使用

需要进行映射,state、dispatch都需要映射

  1. import React, { PureComponent } from 'react';
  2. // 1. 引入connect,用于连接组件和redux,返回一个高阶组件
  3. import { connect } from 'react-redux';
  4. import { changeCounterAction } from '../store/actionCreatores';
  5. export class About extends PureComponent {
  6. changeCounter(num) {
  7. // 4. 从props中取出changeCounter,不再从state中取,因为state中的值已经被映射到props中了
  8. const { changeCounter } = this.props;
  9. // 执行了下面定义的方法,相当于调用了dispatch(changeCounterAction(num))
  10. changeCounter(num);
  11. }
  12. render() {
  13. // 3. 从props中取出counter,不再从state中取,因为state中的值已经被映射到props中了
  14. const { counter } = this.props;
  15. return (
  16. <>
  17. <h2>About : {counter}</h2>
  18. <button onClick={(e) => this.changeCounter(8)}>+8</button>
  19. {' '}
  20. <button onClick={(e) => this.changeCounter(-8)}>-8</button>
  21. </>
  22. );
  23. }
  24. }
  25. /**
  26. * connect => connect()()返回一个高阶组件,第一个()传入两个参数,第二个()传入一个组件,返回一个新的 高阶组件
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/510717
    推荐阅读
    相关标签