React—11—redux


 一、redux概念

◼ JavaScript开发的应用程序,已经变得越来越复杂了:
 JavaScript需要管理的状态越来越多,越来越复杂;
 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示
加载动效,当前分页;
◼ 管理不断变化的state是非常困难的:
 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;
◼ React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理:
 无论是组件定义自己的state,还是组件之间的通信通过props进行传递;也包括通过Context进行数据之间的共享;
 React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定;
◼ Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态管理;
◼ Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)
 
 

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

redux是一个单独的库,所以可以在普通项目、vue、react中使用。

三、react中使用redux

我的理解是,createStore时必须有一个reducer函数,

store会主动调用reducer函数返回的state作为初始state;

 

后续,当我们使用dispatch取修改的时候,redux会主动调用reducer,然后reducer内部去做修改然后返回一个全新的state,

由于createStore是跟reducer强绑定的,所以store的数据也就跟着改变了。

 

 

第一:npm install redux

第二:专门建一个store文件夹,去实例化一个store,并且这个store的数据是由reducer提供的。

第三:其他组件可以通过store.getState取获取store数据的初始值

第四:想要修改store里的值,需要用到store.dispatch({type:'xxxxxx', params);

第五:当我们dispatch时,redux会自动帮助我们调用reducer函数,所以我们需要在reducer函数里做处理

reducer函数有两个参数,一个是旧state,一个是本次提出dispatch的action对象。

经过处理后,返回一个新的state(记住,reducer是一个纯函数,所以不要修改原旧state,要返回一个新的state。)

第六: redux把旧的state更新成reducer新返回的state

第七:通知所有订阅过(store.subcribe())的组件

 

 

复制代码
import { createStore } from 'redux';
import * as actionTypes from './constants';


// 定义常量
export const ADD_NUMBER = 'add_number';
export const SUB_NUMBER = 'sub_number';


// 定义action
export const addNumberAction = num => ({
    type: ADD_NUMBER,
    num
  });
  
  export const subNumberAction = num => ({
    type: SUB_NUMBER,
    num
  });



// 定义reducer函数
const initialState = {
  counter: 0
};
export function reducer(state = initialState, action) {
  switch (action.type) {
    case actionTypes.ADD_NUMBER:
      return { ...state, counter: state.counter + action.num };
    case actionTypes.SUB_NUMBER:
      return { ...state, counter: state.counter - action.num };

    default:
      return state;
  }
}


// 实例化store
const store = createStore(reducer);


export default store;
复制代码

 

复制代码

import React, { PureComponent } from 'react';
import Home from './pages/home';
import Profile from './pages/profile';
import './style.css';
import store from './store';

export class App extends PureComponent { constructor() { super();
this.state = { counter: store.getState().counter }; } componentDidMount() { store.subscribe(() => this.setState({ counter: store.getState().counter })); } render() { const { counter } = this.state; return ( <div> <h1> App {counter} <Home></Home> <Profile></Profile> </h1> </div> ); } } export default App;
复制代码

 

复制代码
import React, { PureComponent } from 'react'
import store from "../store"
import { addNumberAction } from '../store/counter'

export class Home extends PureComponent {
  constructor() {
    super()

    this.state = {
      counter: store.getState().counter,
    }
  }

  componentDidMount() {
    store.subscribe(() => {
      const state = store.getState()
      this.setState({ counter: state.counter })
    })
  }

  addNumber(num) {
    store.dispatch(addNumberAction(num))
  }

  render() {
    const { counter } = this.state

    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <div>
          <button onClick={e => this.addNumber(1)}>+1</button>
          <button onClick={e => this.addNumber(5)}>+5</button>
          <button onClick={e => this.addNumber(8)}>+8</button>
        </div>
      </div>
    )
  }
}

export default Home
复制代码

 

四、使用库react-redux库简化我们的代码

1.npm install react-redux

2.在index.js里引入库提供的provide组件,然后把仓库store放入。

复制代码
import { Provider } from "react-redux"
import store from "./store"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  // </React.StrictMode>
);
复制代码

 

3.我们自己的组件想使用仓库store的数据,也不用store.getState().xxx了,这样很麻烦。

我们先导入react-redux库的connect函数。

通过connect(par1,par2)(Abount)代表,我们将导出一个由react-redux管理的About组件。

其中par1表示要传入一个函数,这个函数的参数为仓库store的state,这个函数的返回值将会加在About组件的props上, 即 <About  diyCounter:state:counter></About>

其中par2表示要传入一个函数,这个函数的参数为仓库store的dispatch,这个函数的返回值将会加在About组件的props上, 即 <About addNumber:addNumber   subNUmber:subNumber></About>

复制代码
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { addNumberAction, subNumberAction } from '../store/counter';
export class About extends PureComponent {
  render() {
    const { diyCounter, addNumber, subNumber } = this.props;
    return (
      <div>
        <h1>About:{diyCounter}</h1>

        <div>
          <button onClick={e => addNumber(1)}>+1</button>
          <button onClick={e => addNumber(5)}>+5</button>
          <button onClick={e => subNumber(1)}>-1</button>
          <button onClick={e => subNumber(5)}>-5</button>
        </div>
      </div>
    );
  }
}

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

const mapDispatchToProps = dispach => ({
  addNumber(num) {
    dispach(addNumberAction(num));
  },
  subNumber(num) {
    dispach(subNumberAction(num));
  }
});
export default connect(mapStateToProps, mapDispatchToProps)(About);
复制代码

 

 

五、使用react-redux库和redux-thunk库进行异步操作数据

这个redux-thunk库主要是帮我们做了一个增强:本来dispatch()只可以传入一个对象{type:'xxx', par},现在增强后,还可以给dispatch里传入一个函数,这样就方便我们进行异步操作。

◼ redux也引入了中间件(Middleware)的概念:
 这个中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码;
 比如日志记录、调用异步接口、添加代码调试功能等等;
◼ 我们现在要做的事情就是发送异步的网络请求,所以我们可以添加对应的中间件:
 这里官网推荐的、包括演示的网络请求的中间件是使用 redux-thunk;
 
◼ redux-thunk是如何做到让我们可以发送异步的请求呢?
 我们知道,默认情况下的dispatch(action),action需要是一个JavaScript的对象;
 redux-thunk可以让dispatch(action可以是函数),action可以是一个函数;
 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数;
✓ dispatch函数用于我们之后再次派发action;
✓ getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态;

 

 

1.npm install redux-thunk;

2.在createStore时,除了传入reducer,也传入这个thunk;

3.在mapDispatchToProps里的dispatch函数,可以传入一个函数,这个函数里进行异步操作。

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { reducer } from './reducer.js';

const store = createStore(reducer, applyMiddleware(thunk));

export default store;

 

复制代码
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { fetchRecommendDdataAction } from '../store/counter';
export class Recommend extends PureComponent {
  componentDidMount() {
    this.props.fetchRecommendDdata();
  }

  render() {
    const { recommend } = this.props;
    return (
      <div>
        <h1>Recommend</h1>

        <ul>
          {recommend.map(e => {
            return <li key={e.title}> {e.title}</li>;
          })}
        </ul>
      </div>
    );
  }
}

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

const mapDispatchToProps = dispach => ({
  fetchRecommendDdata() {
    dispach(fetchRecommendDdataAction());
  }
});
export default connect(mapStateToProps, mapDispatchToProps)(Recommend);
复制代码
复制代码
import { ADD_NUMBER, SUB_NUMBER, GET_RECOMMEND_DATA } from './constants';
import axios from 'axios';


export const fetchRecommendDdataAction = () => {
  return dispatch => {
    axios.get('http://123.207.32.32:8000/home/multidata').then(res => {
      dispatch({ type: GET_RECOMMEND_DATA, recommendData: res.data.data.recommend.list });
    });
  };
};
复制代码

 

 

 

 

六、使用react-redux库和redux-toolkit库进行操作

◼ Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。
 在前面我们学习Redux的时候应该已经发现,redux的编写逻辑过于的繁琐和麻烦。
 并且代码通常分拆在多个文件中(虽然也可以放到一个文件管理,但是代码量过多,不利于管理);
 Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题;
 在很多地方为了称呼方便,也将之称为“RTK”;
 
Redux Toolkit的核心API
 createSlice:接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
 createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected
 configureStore:包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供
基于该承诺分派thunk

 

 

 

 

我们目前redxu写法有点麻烦,可以使用redux-toolkit简化我们的操作,而且这这个库里面也内置redux-thunk,不需要额外安装了。

第一步:npm i @reduxjs/toolkit

store.js

复制代码
import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './features/counter';
import recommendSlice from './features/recommend';

const store = configureStore({
  reducer: {
    counterSlice,
    recommendSlice
  }
});

export default store;
复制代码

 

recucer函数

复制代码
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: {
    counter: 0
  },
  reducers: {
    addNumberAction(state, action) {
      // 使用redux-toolkit,这里无需返回一个新的对象,库内部会帮我们返回一个新的对象,我们这里直接修改原state即可。
      state.counter += action.payload;
    },
    subNumberAction(state, action) {
      state.counter -= action.payload;
    }
  }
});

export const { addNumberAction, subNumberAction } = counterSlice.actions;
export default counterSlice.reducer;
复制代码

 

Home.jsx

这里面要注意的是,dispatch的action,不需要我们自己定义了,reducer.js里定义的action可以通过export const { addNumberAction, subNumberAction } = counterSlice.actions;导出,

然后组件里直接使用即可,库内部会帮助我们做好匹配。

复制代码
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { addNumberAction } from '../store/features/counter';

export class Home extends PureComponent {
  addNumber(num) {
    this.props.addNumber(num);
  }

  render() {
    const { counter } = this.props;
    console.log('%c [ counter ]-12', 'font-size:13px; background:pink; color:#bf2c9f;', counter)

    return (
      <div>
        <h2>Home Counter: {counter}</h2>
        <div>
          <button onClick={e => this.addNumber(1)}>+1</button>
          <button onClick={e => this.addNumber(5)}>+5</button>
          <button onClick={e => this.addNumber(8)}>+8</button>
        </div>
      </div>
    );
  }
}
const mapStateToProps = state => ({
  counter: state.counterSlice.counter
});
const mapDispatchToProps = dispatch => ({
  addNumber(num) {
    dispatch(addNumberAction(num));
  }
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
复制代码

 

如果想异步操作,需要这样写:

在reducers外面定义一个action,createAsyncThunk就是redux-toolkit里内置了reduxthunk的功能。

fetchRecommendDdataAction = createAsyncThunk()

这个函数
createAsyncThunk()第一个参数传递名字,第二个参数要传递一个回调函数,回调函数的payload是组件调用时可以传递的参数,store时仓库实例,可以通过store.dispatch(changeRecommendAction(res.data.data.recommend.list))继续调用reducer里的方法
取修改仓库。
或者用extraReducers修改,也可以。
复制代码
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

export const fetchRecommendDdataAction = createAsyncThunk('fetch/recommendData', async (payload, store) => {
  const res = await axios.get('http://123.207.32.32:8000/home/multidata');
  // 方式一:直接调用reducers里的action进行改变
  // store.dispatch(changeRecommendAction(res.data.data.recommend.list));
  // 方式二: 这里return数据,然后在extraReducers里处理
  return res.data.data;
});

const recommendSlice = createSlice({
  name: 'recommendSlice',
  initialState: {
    recommend: [{ title: '是全全美' }]
  },
  reducers: {
    changeRecommendAction(state, action) {
      // 使用redux-toolkit,这里无需返回一个新的对象,库内部会帮我们返回一个新的对象,我们这里直接修改原state即可。
      state.recommend = action.payload;
    }
  },
  extraReducers(builder) {
    builder.addCase(fetchRecommendDdataAction.pending, (state, action) => {
    }).addCase(fetchRecommendDdataAction.fulfilled, (state, action) => {
      state.recommend = action.payload.recommend.list;
    })
  }
});

export const { changeRecommendAction } = recommendSlice.actions;

export default recommendSlice.reducer;
复制代码

 

 

 

 

 

七、react-redux的connect()的原理

 

 

 

八、redux的数据不可以变性

为什么我们可以在reduces中,直接通过state.counter  += action.num去改变值,而不是返回一个新的对象?
因为库内部使用immerjs帮助我们返回了一个新的对象。

 

 

 

九、通过中间件加一些功能

 

 

posted @   Eric-Shen  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示