Redux详解

在这里插入图片描述


简介

Redux 是一个专门用于状态管理的JS库(不是 React 插件库)。 可以理解为全局数据状态管理工具(状态管理机),用来做组件通信等。集中式管理 React 应用中多个组件共享的状态

类似于vue的状态管理 vuex

你可能不需要 Redux

首先明确一点,Redux 是一个有用的架构,但不是非用不可。事实上,大多数情况,可以不用它,只用 React 就够了。

鲁迅(∩_∩) 曾经说过: “如果不知道是否需要 Redux,那就是不需要它。”
Redux 的创造者 Dan Abramov 又补充了一句:“只有遇到 React 实在解决不了的问题,才需要 Redux 。”

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。

不需要使用 Redux:

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

需要使用 Redux:

  • 组件需要共享数据(状态state)的时候
  • 某个状态需要在任何地方都可以被随时访问的时候
  • 某个组件需要改变另一个组件的状态的时候

具体使用场景如:语言切换、黑暗模式切换、用户登录全局数据共享…


Redux 原理

  1. Redux会将整个应用状态(其实也就是数据)存储到到一个地方,称为store.这个store里面保存一棵状态树(state tree)
  2. 组件改变state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,于是state完成更新
  3. 组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件
  4. 其它组件可以通过订阅store中的状态(state)来刷新自己的视图

在这里插入图片描述

.

Redux架构原理:

  1. 剥离组件数据(state)
  2. 数据统一存放在store中
  3. 组件订阅store获得数据
  4. store推送数据更新

一句话总结:Redux统一保存了数据,在隔离了数据与UI的同时,负责处理数据的绑定。


Redux三大原则

  1. 单一数据源

    • Redux 应用中只有一个全局的、可观察的状态对象。这个状态对象通常被称为“store”。

    • 整个应用的状态都存储在这个单一的 store 中,并且这个状态是只读的。

    • 这意味着任何对状态的修改都必须通过发送 action 来触发 reducer 进行更新。

  2. 状态是只读的

    • Redux 中的 store 状态是只读的,这意味着不能直接修改存储在 store 中的状态。

    • 当需要改变状态时,需要发送一个 action。这个 action 是一个描述发生了什么的普通 JavaScript 对象。

    • 然后,Redux 会将 action 和当前的 state 传递给一个 reducer 函数。Reducer 函数接收当前的 state 和 action,然后返回一个新的 state。

    • 新的 state 随后会被 Redux 保存到 store 中,并且这个新的 state 会自动触发所有订阅了 store 的组件进行更新。

  3. 使用纯函数来执行修改

    • Reducer 函数是纯函数,这意味着它们接收一些输入,返回一些输出,并且没有任何其他的副作用(如 API 调用、DOM 修改等)。

    • 这意味着对于相同的输入,reducer 总是返回相同的输出。这有助于保持应用程序的可预测性和可测试性。

    • 由于 reducer 是纯函数,因此它们可以很容易地进行单元测试,以确保它们按预期工作。



三大核心

Redux 的三大核心为 Store、Action 和 Reducer。这三个部分共同协作,实现了 Redux 状态管理的核心功能。

下面以这三大核心为例,实现语言切换的功能

安装:

  • yarn add redux
  • npm install redux --save

新建文件夹:

  • 在src下新建redux文件夹
    在这里插入图片描述

1. Store

  • Store 是 Redux 应用中唯一的数据源。它保存着整个应用的状态,并且这个状态是只读的。

  • 可以使用 createStore() 方法来创建一个 Redux Store,并传入一个 Reducer 作为初始参数。

  • Store 提供了一系列方法来查询当前的状态、派发动作(actions)以及注册监听器来监听状态的变化。

  • 在应用中,通常会:
    通过 store.getState() 获取到store里面所有的数据内容
    通过 store.dispatch(action) 来派发一个action动作,这个action会传递给store
    通过 store.subscribe(listener) 来注册一个监听器。 可以订阅(监听) store的改变 只要store发生改变, 这个方法的回调函数就会执行

在redux文件夹下新建store.js

import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer); // createStore可以帮助我们创建一个store

export default store;

核心: store 通过 dispatch 方法来通知 reducer 执行一个动作(action)以更新 store 中的数据,遵循了单向数据流的原则。
store.dispatch(addLanguage({ name: '新的语言', code: 'new_lang' }));

.

2. Action

  • Action 是一个描述发生了什么的普通 JavaScript 对象。它通常包含一个 type 字段来表示动作的类型,以及其他一些字段来表示与动作相关的数据。

  • 一个表示添加待办事项的动作可能看起来像这样:{ type: 'ADD_TODO', text: 'Learn about Redux' }
    例如:吃面条type 字段来表示动作的类型 name 字段来表示与动作相关的数据 面条
    即:{ type: '吃(动作)', text: '面条(值)' }

  • 动作由 Store 的 dispatch() 方法派发出去,然后 Redux 会使用 Reducer 来根据这个动作更新应用的状态。

在redux文件夹下新建action.js

export const changeLanguage = value => ({ type: 'CHANGELANGUAGE', value });
export const addLanguage = value => ({ type: 'ADDLANGUAGE', value });

.
Redux 的 Action 相当于 Vue 中 在组件里的 commit 一个 mutations

import { useStore } from 'vuex';

const store = useStore();

const changeLanguage = value => {
     store.commit("Change_Language", value );
}
const addLanguage = value => {
     store.commit("Add_Language", value );
}

.

3. Reducer

  • 用于初始化状态加工状态,产生新状态state的纯函数 (只要是同样的输入,必定得到同样的输出)

  • Reducer 是一个函数,它接收 当前的状态state动作对象action 作为参数,并返回一个新的 state

  • 在 Redux 应用中,可以有多个 Reducer,每个 Reducer 负责管理应用状态树中的一部分。可以使用 combineReducers() 方法来将它们组合成一个根 Reducer。

在redux文件夹下新建reducer.js

import i18n from 'i18next';

// 初始化状态
const initState = {
    language: 'zh',
    languageList: [
        { name: '中文', code: 'zh' },
        { name: 'English', code: 'en' }
    ],
};

const reducer = (state = initState, action) => {
	// 根据type动作决定如何加工数据
    switch (action.type) {
        case 'CHANGELANGUAGE': // 切换语言的动作
            i18n.changeLanguage(action.value);
            return { ...state, language: action.value }; // 重点:不能直接修改旧的state,而是返回一个新的state来替代
        case 'ADDLANGUAGE': // 新增语言的动作
            return { ...state, languageList: [...state.languageList, action.value] };
        default:
            return state;
    }
};

export default reducer;

Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。

.
Redux 的 Reducer 相当于 Vuex 中的 State + Mutations

import { createStore } from 'vuex'

export default createStore({
	// 初始化状态
    state: {
	    language: 'zh',
	    languageList: [
	        { name: '中文', code: 'zh' },
	        { name: 'English', code: 'en' }
	    ],
    },
    // 修改state中的数据
    mutations:{
        Change_Language(state,value){
            state.language = value;
        },
        Add_Language(state,value){
            state.languageList = [...state.languageList, value];
        },
    }
}   

组件中使用

核心三步:

  1. 更新store
    store 通过 dispatch 方法来通知 reducer 执行一个动作( action ),以更新 store 中的数据

    store.dispatch(addLanguage({ name: '新的语言', code: 'new_lang' }));
    
  2. 订阅store
    当 store 发生改变时,自动触发自定义方法 storeChange,以此更新 state 中的数据

    store.subscribe(this.storeChange.bind(this));
    
  3. 渲染View
    当store发生改变时,自动触发,得到最新的store,并更新到state中,从而重新渲染 View

    storeChange() {
       let storeState = store.getState();
       this.setState({
           language: storeState.language,
           languageList: storeState.languageList
       }); 
    }
    

完整步骤:

语言切换组件Language.jsx

import React from 'react';
import { withRouter } from 'react-router-dom';
import store from '../../redux/store';
import { changeLanguage, addLanguage } from '../../redux/action';

class LanguageComponents extends React.Component{
    constructor(props) {
        super(props);
        // 最开始获取初始值
        const storeState = store.getState();
        this.state = {
            language: storeState.language,
            languageList: storeState.languageList
        }
        
		// 2. 订阅store,当store发生改变时,自动触发storeChange方法,以此更新state中的数据,从而重新渲染 View
        store.subscribe(this.storeChange.bind(this));
    }
    storeChange() {
        // 3. 当store发生改变时,自动触发,得到最新的store,并更新到state中,从而重新渲染 View
        let storeState = store.getState();
        this.setState({
            language: storeState.language,
            languageList: storeState.languageList
        }); 
    }
    handleChange = e => {
        // 1. store通过dispatch方法来通知reducer执行一个动作(action),以更新 store 中的数据
        let current = this.props.languageList.find((option) => option.code === e.target.value);
        store.dispatch(changeLanguage(current));   
    }
    render() {
        return (
            <div>
                <select onChange={this.handleChange}>
                    {this.state.languageList.map((item) => {
                        return <option key={item.code} value={item.code}>{item.name}</option>
                    })}
                </select>
            	当前语言是{this.state.language === 'zh' ? '中文' : 'English'}
            </div >
        );
    }
};

export const Language = withRouter(LanguageComponents);


React-Redux

  • Redux 是一个独立的状态管理库(非官方),提供了状态管理的核心概念和方法。可以与任何JavaScript应用程序一起使用

  • React-Redux官方专门为React应用程序提供的Redux绑定库,使得状态管理在React应用程序中更加无缝和方便

特性/库Reduxreact-redux
用途独立的状态管理库React应用程序的Redux绑定库
与React的集成需要手动集成提供与React组件集成的功能
获取状态通过 store.getState() 获取使用 mapStateToProps 从props中获取
触发action通过 store.dispatch(action) 触发使用 mapDispatchToProps 绑定actions到props上
连接组件与store手动在组件内部连接使用 Providerconnect 函数连接
中间件支持原生支持,如 Redux Thunk通过Redux原生支持
异步操作需要使用中间件,如 Redux Thunk 或 Redux Saga依然需要Redux中间件,但react-redux提供了更好的与React组件的集成
适用场景任何需要状态管理的JavaScript应用程序特别是React应用程序,需要更好地组织、管理和更新状态
代码量较少,专注于状态管理核心稍多,因为需要与React组件集成
社区支持广泛,有大量教程和库活跃,有大量的React开发者使用

1. 安装

需要安装 reduxreact-redux

npm install redux react-redux

yarn add redux react-redux

2. 创建 Redux Store

这里不变,与上面三大核心一样:

  • store.js

    import { createStore } from 'redux';
    import reducer from './reducer';
    
    const store = createStore(reducer);
    
    export default store;
    
  • actions.js

    export const changeLanguage = value => ({ type: 'CHANGELANGUAGE', value });
    export const addLanguage = value => ({ type: 'ADDLANGUAGE', value });
    
  • reducer.js

    import i18n from 'i18next';
    
    // 初始化状态
    const initState = {
        language: 'zh',
        languageList: [
            { name: '中文', code: 'zh' },
            { name: 'English', code: 'en' }
        ],
    };
    
    const reducer = (state = initState, action) => {
    	// 根据type动作决定如何加工数据
        switch (action.type) {
            case 'CHANGELANGUAGE': // 切换语言的动作
                i18n.changeLanguage(action.value);
                return { ...state, language: action.value }; // 重点:不能直接修改旧的state,而是返回一个新的state来替代
            case 'ADDLANGUAGE': // 新增语言的动作
                return { ...state, languageList: [...state.languageList, action.value] };
            default:
                return state;
        }
    };
    
    export default reducer;
    

3. 使用 Provider 包裹根组件

使用 Provider 组件包裹根组件,并传入store,这样整个应用就可以访问 Redux store 了。

// index.js 或 App.js  
import React from 'react';  
import ReactDOM from 'react-dom';  
import { Provider } from 'react-redux';  
import store from './store';  
import App from './App';  
  
ReactDOM.render(  
  {/* 给组件App传递store */}
  <Provider store={store}>  
    <App />  
  </Provider>,  
  document.getElementById('root')  
);

4. 在组件中连接 Redux Store

import React from 'react';  
import { connect } from 'react-redux';  
import { withRouter } from 'react-router-dom';  
import { changeLanguage, addLanguage } from '../../redux/action';  
  
// 定义一个纯React组件  
class LanguageComponent extends React.Component {  

    handleChange = (e) => {  
        // 直接调用props中的actions  
        let current = this.props.languageList.find((option) => option.code === e.target.value)
        this.props.changeLanguage(current);
    }  
  
    render() {  
        // 直接使用props中的language和languageList  
        return (  
            <div>  
                <select onChange={this.handleChange}>
                    {this.props.languageList.map((item) => {
                        return <option key={item.code} value={item.code}>{item.name}</option>
                    })}
                </select>
                当前语言是{this.props.language === 'zh' ? '中文' : 'English'}  
            </div>  
        );  
    }  
}  
  

// mapStateToProps接收Redux state并返回组件需要的props  
const mapStateToProps = (state) => ({  
    language: state.language,  
    languageList: state.languageList  
});  

// mapDispatchToProps接收dispatch函数并返回与actions绑定的props  
const mapDispatchToProps = {  
    changeLanguage,  
    addLanguage  
};  
  
// 使用connect函数将Redux store连接到组件  
const LanguageConnected = connect(mapStateToProps, mapDispatchToProps)(LanguageComponent);  
  
// 使用withRouter来包裹连接后的组件  
export const Language = withRouter(LanguageConnected);

在这个改写的版本中,LanguageComponent 是一个纯 UI组件,Language 是一个容器组件。

.

connect函数

connect函数 负责订阅 Redux store,当 store 更新时,mapStateToPropsmapDispatchToProps 定义的函数会被调用,并更新组件的 props,从而触发组件的重新渲染

  • mapStateToProps(映射状态)

    mapStateToProps 是一个函数,它接收Redux store的state作为参数,并返回一个对象,这个对象中的属性会作为props传递给组件(languagelanguageList)。

    const mapStateToProps = (state) => {
    	return{  
    	    language: state.language,  
    	    languageList: state.languageList  
    	}
    };  
    

    或简写成(根据ES6简写):

    const mapStateToProps = state => ({  
    	    language: state.language,  
    	    languageList: state.languageList  
    })  
    
  • mapDispatchToProps(映射操作状态的方法)

    mapDispatchToProps 也是一个函数,它接收dispatch函数作为参数,并返回一个对象,这个对象中的方法会作为props传递给组件(changeLanguageaddLanguage),并且这些方法已经用dispatch进行了包装,可以直接调用以触发action。

    const mapDispatchToProps = (dispatch) => {  
        return {  
            changeLanguage: (val) => dispatch(changeLanguage(val)),  
            addLanguage: () => dispatch(addLanguage({ name: '新的语言', code: 'new_lang' })),  
        };  
    };  
    

    或简写成(根据API简写):

    const mapDispatchToProps = {  
        changeLanguage,  // 自动dispatch
        addLanguage  
    };  
    

    当把 changeLanguageaddLanguage action creators作为对象的属性直接赋值给 mapDispatchToProps 时,react-redux 的 connect函数 会自动将它们与 Redux 的 dispatch函数 进行绑定,使得可以在组件的 props 中直接调用这些 action creators。

mapStateToPropsmapDispatchToProps 已经被定义并传递给了connect函数connect函数Redux store 连接到组件,所以 LanguageComponent 会自动接收languagelanguageListchangeLanguageaddLanguage 作为props,不再直接访问 Redux store

也就是说 react-redux 中不需要直接使用 store.getState()store.dispatch()store.subscribe(),而是通过react-redux提供的API(如connect函数、useDispatch、useSelector等)来间接地与Redux store进行交互。这些API为我们提供了更加简洁和易用的方式来管理Redux状态,使得我们可以更加专注于组件的逻辑和渲染。

clickHandler = (e) => {
    // redux中:store通过dispatch方法来通知reducer执行一个动作(action),以更新 store 中的数据
    let val = e.key;
	store.dispatch(changeLanguage(val));
	
	
	// react-redux中: 不通过store的dispatch方法,直接通过props
	this.props.changeLanguage(val)
}

React-Redux 已经为我们处理了订阅和重新渲染的逻辑。
当store中的状态发生变化时,connect会自动触发组件的重新渲染,而不需要我们手动使用store.subscribe来订阅状态的变化。

.

redux 与 react-redux 在组件中使用的区别:

// redux中: 订阅store,当store发生改变时,自动触发storeChange方法,以此更新state中的数据,从而重新渲染 View
store.subscribe(this.storeChange.bind(this));

storeChange() { 
	let storeState = store.getState(); 
	this.setState({ 
		language: storeState.language, 
		languageList: storeState.languageList 
	}); 
}



// react-redux中:直接使用props中的language和languageList  
// 不用store.subscribe订阅,也不用store.getState()获取store中的数据
render() {  
        return (  
            <div>  
                <Menu onClick={this.clickHandler}>  
                    {this.props.languageList.map((item) => {  
                        return <Menu.Item key={item.code}>{item.name}</Menu.Item>;  
                    })}  
                </Menu>  
                当前语言是{this.props.language === 'zh' ? '中文' : 'English'}  
            </div>  
        );  
    }  


其他

合并多个 reducer(combineReducers)

  • combineReducers 是 Redux 库中的一个实用函数,用于将多个 reducer 函数合并成一个单一的 reducer 函数
  • 当 Redux 应用的状态变得复杂时,通常需要将状态拆分成多个独立的 slice,每个 slice 由一个单独的 reducer 函数管理。
  • combineReducers 用来帮助将这些独立的 reducer 函数合并起来,以管理整个应用的状态
  1. 首先,如果定义多个独立的 reducer 函数,每个函数负责管理状态树中的一个特定部分(slice)。

    切换语言的 reducer :/src/redux/reducers/language.js

    // 初始化状态
    const initState = {
        currentLanguage: {
            name: '简体中文',
            code: 'zh-CN'
        },
        languageList: [
            { name: '简体中文', code: 'zh-CN' },
            { name: '繁體中文', code: 'zh-HK' },
            { name: 'English', code: 'en-US' },
            { name: '日本語', code: 'ja-JP' }
        ]
    };
    
    const reducer = (state = initState, action) => {
        switch (action.type) {
            case 'CHANGELANGUAGE':
                return { ...state, currentLanguage: action.value };
            default:
                return state;
        }
    };
    
    export default reducer;
    

    切换货币的 reducer :/src/redux/reducers/currency.js

    // 初始化状态
    const initState = {
        currentCurrency: {
            name: '人民币',
            currency: 'CNY'
        },
        currencyList: [
            { name: '人民币', currency: 'CNY' },
            { name: '港币', currency: 'HKD' },
            { name: '美元', currency: 'USD' },
            { name: '日元', currency: 'JPY' }
        ]
    };
    
    const reducer = (state = initState, action) => {
        switch (action.type) {
            case 'CHANGECURRENCY':
                return { ...state, currentCurrency: action.value };
            default:
                return state;
        }
    };
    
    export default reducer;
    
  2. 使用 combineReducers 将这些独立的 reducer 函数合并成一个单一的 reducer 函数

    combineReducers 汇总所有的 reducer,变成一个总的 reducers

    /src/redux/reducers/index.js

    import { combineReducers } from 'redux';
    
    import languageReducer from './language';
    import currencyReducer from './currency';
    
    const allReducer = combineReducers({
        languageReducer,
        currencyReducer
    });
    
    export default allReducer;
    
  3. 在 Redux store 中使用合并后的 reducer。

    /src/redux/store.js

    import { createStore } from 'redux';  
    import allReducer from './reducers'; // 引入合并后的 reducer  
      
    const store = createStore(allReducer);  
      
    export default store;
    
  4. 使用 Provider 包裹根组件

    // index.js 或 App.js  
    import React from 'react';  
    import ReactDOM from 'react-dom';  
    import { Provider } from 'react-redux';  
    import store from './store';  
    import App from './App';  
      
    ReactDOM.render(  
      <Provider store={store}>  
        <App />  
      </Provider>,  
      document.getElementById('root')  
    );
    
  5. 在组件中连接 Redux Store

    import React from 'react';  
    import { connect } from 'react-redux';  
    import { withRouter } from 'react-router-dom';  
      
    class AllReducerComponent extends React.Component {  
        render() {  
            return (              
                <div>
                    <h3>当前语言是:{this.props.currentLanguage.name} / {this.props.currentLanguage.code}</h3>
                    <h3>当前货币是:{this.props.currentCurrency.name} / {this.props.currentCurrency.currency}</h3>
                </div>
            );  
        }  
    }  
      
    
    // mapStateToProps接收Redux state并返回组件需要的props  
    const mapStateToProps = (state) => ({
        currentLanguage: state.languageReducer.currentLanguage,
        currentCurrency: state.currencyReducer.currentCurrency,
    });
    
    // 使用connect函数将Redux store连接到组件  
    const AllReducerConnected = connect(mapStateToProps)(AllReducerComponent);  
      
    // 使用withRouter来包裹连接后的组件  
    export const AllReducer = withRouter(AllReducerConnected);
    

注意:

  1. 每个 reducer 都应该是一个纯函数,即它不应该修改传入的参数,也不应该有任何副作用。

  2. combineReducers 调用时,它会为每个 reducer 传入当前状态的对应 slice 和 action。
    如果 action 不属于某个 reducer,那么该 reducer 应该返回其当前状态。

  3. 合并后的 reducer 产生的状态树的结构与传递给 combineReducers 的对象的结构相同。

  4. combineReducers 只是一个工具函数,用于简化 Redux reducer 的编写。
    完全可以不使用它,而是编写自定义的 reducer 来处理更复杂的状态管理需求。

类似于 vuex 中的 modules

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import * as getters from './getters'

import moduleA from './module/moduleA' // 模块A
import moduleB from './module/moduleB' // 模块B

Vue.use(Vuex)

export default new Vuex.Store({
    actions,
    getters,
    state,
    mutations,
    modules: {
        moduleA,
        moduleB
    }
})

异步 action(redux-thunk)

  • 同步action:是一个简单的JavaScript对象

  • 异步action通常是一个返回函数的函数(即高阶函数),该函数会在内部执行异步操作,如发送网络请求、获取数据等。

    异步action 再次调用 dispatch 函数来发起一个同步 action,该同步 action 会被传递给 reducer 进行处理。
    因此,目标state并不会立即响应,而是要等待异步操作完成后,再通过同步action来更新state

特点:

  • 异步action不会立即触发状态的变化。

  • 它们允许在Redux中处理异步操作,如网络请求或执行异步操作。

  • 异步action通常使用Redux中间件(如redux-thunk、redux-saga等)来处理。

  • 当异步操作完成时,异步action会通过发送同步action来触发状态的更新。

  1. 创建异步action函数:

    在action.ts中

    // 同步action
    export const changeLanguage = value => ({ type: 'CHANGELANGUAGE', value });
    export const addLanguage = value => ({ type: 'ADDLANGUAGE', value });
    
    // 异步action
    // 定义一个异步action的创建函数  
    export const addLanguageAsync = () => (dispatch) => {  
      	// 异步操作(例如网络请求)  
    	axios.post('https://api.example.com/submit')  
    		.then(function (response) {  
    			const value = response.data; // 从服务器获取的数据
    			
    			// 提交一个'ADDLANGUAGE'的action,通知store更新值
    			dispatch({ type: 'ADDLANGUAGE', value });  
    			
    			// 也可以直接调用上面的同步action
    			dispatch(addLanguage(value));  
    		}) 
    };  
    
  2. 使用 redux-thunk

    使用 Redux 的中间件 redux-thunk,它允许在 Redux 的 action 创建函数中返回函数,而不是像通常那样返回一个 action 对象。这个特性使得可以进行异步操作,比如 API 调用,然后在这些操作完成后派发(dispatch)一个或多个 action。

    安装 redux-thunk

    • npm install redux-thunk

    • yarn add redux-thunk

  3. 配置Redux Store:

    在应用Redux Store时,需要使用 applyMiddleware 函数来应用 redux-thunk 中间件。

    在 store.ts 中

    import { createStore, applyMiddleware } from 'redux';  
    import thunk from 'redux-thunk';  
    import reducers from './reducers'; 
    
    const store = createStore(reducers, applyMiddleware(thunk));
    
    export default store;
    
  4. 在组件中调用异步Action:

    import store from '../../redux/store';
    import { addLanguageAsync } from '../../redux/action';
    
    clickHandler = () => {
    	store.dispatch(addLanguageAsync(val));
    }
    

Redux DevTools 插件

  1. 浏览器中安装 redux-devtools 插件

    在这里插入图片描述

  2. 项目中安装 redux-devtools-extension

    yarn add redux-devtools-extension

  3. 项目中使用
    在 store.js 中

    import {composeWithDevTools} from 'redux-devtools-extension'
    import { createStore, applyMiddleware } from 'redux';  
    import thunk from 'redux-thunk';  
    import reducers from './reducers'; 
    
    // 旧
    // const store = createStore(reducers, applyMiddleware(thunk));
    
    // 使用composeWithDevTools
    // 如果没有异步action,则不需要redux-thunk
    // const store = createStore(reducers,composeWithDevTools());
    const store = createStore(reducers,composeWithDevTools(applyMiddleware(thunk)));
    
    export default store;
    
  4. 在浏览器中调试

    浏览器中 redux-devtools 插件的图标已经点亮

    浏览器的调试窗口出现 redux 选项卡,里面可以实时观测 store

    在这里插入图片描述

打包部署

  1. 打包

    yarn build

    项目中将生成 build文件夹

  2. 全局安装 serve

    yarn global add serve

    npm i serve -g

  3. 启动 serve

    进入build文件夹中,直接运行serve(最好在 PowerShell 中运行),即可本地运行打包后的文件

    在这里插入图片描述
    这里的图标也就变成了生产环境了

    在这里插入图片描述


完整项目

目录结构:
在这里插入图片描述

  • src/index.js

    React的 入口文件,它负责初始化并渲染React组件到DOM中。

    • <React.StrictMode>:启用一些额外的检查和警告,帮助开发者在开发阶段捕获可能的错误。只在开发模式下运行,不会与生产模式冲突。

    • <BrowserRouter>:使用 HTML5 的 History API

    • <Provider store={store}>:允许在组件中的任何位置访问 Redux store。

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from 'react-router-dom';
    import { Provider } from 'react-redux';
    import App from './App';
    import store from './redux/store';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
        <React.StrictMode>
            <BrowserRouter>
                <Provider store={store}>
                    <App />
                </Provider>
            </BrowserRouter>
        </React.StrictMode>
    );
    
  • src/App.jsx

    React的 根组件,所有的其他组件都可能是这个组件的子组件或更深层次的子组件。

    import React, { Component } from 'react';
    import Language from "./views/language";
    import Currency from "./views/currency";
    import Header from "./components/Header";
    
    export default class Hello extends Component {
        render () {
            return (
                <div>
                    <Header />
                    <br />
                    <Language />
                    <br />
                    <Currency />
                </div>
            );
        }
    }
    
  • src/views/language/index.jsx

    <Language /> 组件中使用 react-redux

    • 通过 this.props.languageList 获取 languageReducer这个 reducer 中的 languageList 数据
    • 通过 this.props.currentLanguage 获取 languageReducer这个 reducer 中的 currentLanguage 数据
    • 通过 this.props.changeLanguage(current) 自动触发 dispatch(changeLanguage (current))
    • languageReducer这个 reducer 就是 src/redux/reducers/language.js
    import React, { Component } from 'react'
    import { connect } from 'react-redux';
    import { changeLanguage } from '../../redux/action';
    
    class language extends Component {
    
        handleChange = e => {
            let current = this.props.languageList.find((option) => option.code === e.target.value)
            this.props.changeLanguage(current);
        }
    
        render () {
            return (
                <div>
                    切换语言:
                    <select onChange={this.handleChange}>
                        {this.props.languageList.map((item) => {
                            return <option key={item.code} value={item.code}>{item.name}</option>
                        })}
                    </select>
                    &nbsp;
    
                    {this.props.currentLanguage.name} / {this.props.currentLanguage.code}
                </div>
            );
        }
    }
    
    
    const mapStateToProps = (state) => ({
        currentLanguage: state.languageReducer.currentLanguage,
        languageList: state.languageReducer.languageList
    });
    
    const mapDispatchToProps = {
        changeLanguage
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(language);
    
  • src/views/currency/index.jsx

    <Currency/> 组件中使用 react-redux

    • 通过 this.props.currencyList 获取 currencyReducer这个 reducer 中的 currencyList 数据
    • 通过 this.props.currentCurrency 获取 currencyReducer这个 reducer 中的 currentCurrency 数据
    • 通过 this.props.changeCurrency (current) 自动触发 dispatch(changeCurrency (current))
    • currencyReducer这个 reducer 就是 src/redux/reducers/currency.js
    import React, { Component } from 'react'
    import { connect } from 'react-redux';
    import { changeCurrency } from '../../redux/action';
    
    class currency extends Component {
    
        handleChange = e => {
            let current = this.props.currencyList.find((option) => option.currency === e.target.value)
            this.props.changeCurrency(current);
        }
    
        render () {
            return (
                <div>
                    切换货币:
                    <select onChange={this.handleChange}>
                        {this.props.currencyList.map((item) => {
                            return <option key={item.currency} value={item.currency}>{item.name}</option>
                        })}
                    </select>
                    &nbsp;
    
                    {this.props.currentCurrency.name} / {this.props.currentCurrency.currency}
                </div>
            );
        }
    }
    
    
    const mapStateToProps = (state) => ({
        currentCurrency: state.currencyReducer.currentCurrency,
        currencyList: state.currencyReducer.currencyList
    });
    
    const mapDispatchToProps = {
        changeCurrency
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(currency);
    
  • src/components/Header.jsx

    <Header/> 组件中使用 react-redux

    • 通过 this.props.currentLanguage 获取 languageReducer这个 reducer 中的 currentLanguage 数据
    • 通过 this.props.currentCurrency 获取 currencyReducer这个 reducer 中的 currentCurrency 数据
    • 这个组件中获取了两个不同 reducer 的数据
    import React, { Component } from 'react'
    import { connect } from 'react-redux';
    
    class Header extends Component {
        render () {
            return (
                <div>
                    <h3>当前语言是:{this.props.currentLanguage.name} / {this.props.currentLanguage.code}</h3>
                    <h3>当前货币是:{this.props.currentCurrency.name} / {this.props.currentCurrency.currency}</h3>
                </div>
            )
        }
    }
    
    const mapStateToProps = (state) => ({
        currentLanguage: state.languageReducer.currentLanguage,
        currentCurrency: state.currencyReducer.currentCurrency,
    });
    
    export default connect(mapStateToProps)(Header);
    
  • src/redux/store.js

    • composeWithDevTools():项目中使用 redux开发者调试工具
    • applyMiddleware(thunk): 允许在 Redux 中执行异步 action 操作的中间件
    import { composeWithDevTools } from 'redux-devtools-extension';
    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import reducer from './reducers';
    
    const store = createStore(reducers,composeWithDevTools(applyMiddleware(thunk)));
    
    export default store;
    
  • src/redux/action.js

    定义 同步action 和 异步action

    // 同步action
    export const changeLanguage = value => ({ type: 'CHANGELANGUAGE', value });
    export const changeCurrency = value => ({ type: 'CHANGECURRENCY', value });
    export const addLanguage = value => ({ type: 'ADDLANGUAGE', value });
    
    // 异步action
    // 定义一个异步action的创建函数
    export const addLanguageAsync = () => dispatch => {
        // 异步操作(例如网络请求)
        fetch('https://api.example.com/data').then(function (response) {
            const value = response.data; // 从服务器获取的数据
    
            // 提交一个'ADDLANGUAGE'的action,通知store更新值
            // dispatch({ type: 'ADDLANGUAGE', value });
    
            // 也可以直接调用上面的同步action
            dispatch(addLanguage(value));
        });
    };
    
  • src/redux/reducers/language.js

    定义 language 的 reducer

    // 初始化状态
    const initState = {
        currentLanguage: {
            name: '简体中文',
            code: 'zh-CN'
        },
        languageList: [
            { name: '简体中文', code: 'zh-CN' },
            { name: '繁體中文', code: 'zh-HK' },
            { name: 'English', code: 'en-US' },
            { name: '日本語', code: 'ja-JP' }
        ]
    };
    
    const reducer = (state = initState, action) => {
        switch (action.type) {
            case 'CHANGELANGUAGE':
                return { ...state, currentLanguage: action.value };
            default:
                return state;
        }
    };
    
    export default reducer;
    
  • src/redux/reducers/currency.js

    定义 currency 的 reducer

    // 初始化状态
    const initState = {
        currentCurrency: {
            name: '人民币',
            currency: 'CNY'
        },
        currencyList: [
            { name: '人民币', currency: 'CNY' },
            { name: '港币', currency: 'HKD' },
            { name: '美元', currency: 'USD' },
            { name: '日元', currency: 'JPY' }
        ]
    };
    
    const reducer = (state = initState, action) => {
        switch (action.type) {
            case 'CHANGECURRENCY':
                return { ...state, currentCurrency: action.value };
            default:
                return state;
        }
    };
    
    export default reducer;
    
  • src/redux/reducers/index.js

    合并两个 reducer

    import { combineReducers } from 'redux';
    
    import languageReducer from './language';
    import currencyReducer from './currency';
    
    const allReducer = combineReducers({
        languageReducer,
        currencyReducer
    });
    
    export default allReducer;
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫老板的豆

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值