文章目录
简介
Redux 是一个专门用于状态管理的JS库(不是 React 插件库)。 可以理解为全局数据状态管理工具(状态管理机),用来做组件通信等。集中式管理 React 应用中多个组件共享的状态。
类似于vue的状态管理 vuex
你可能不需要 Redux
首先明确一点,Redux 是一个有用的架构,但不是非用不可。事实上,大多数情况,可以不用它,只用 React 就够了。
鲁迅(∩_∩) 曾经说过: “如果不知道是否需要 Redux,那就是不需要它。”
Redux 的创造者 Dan Abramov 又补充了一句:“只有遇到 React 实在解决不了的问题,才需要 Redux 。”
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
不需要使用 Redux:
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
需要使用 Redux:
- 组件需要共享数据(状态state)的时候
- 某个状态需要在任何地方都可以被随时访问的时候
- 某个组件需要改变另一个组件的状态的时候
具体使用场景如:语言切换、黑暗模式切换、用户登录全局数据共享…
Redux 原理
- Redux会将整个应用状态(其实也就是数据)存储到到一个地方,称为store.这个store里面保存一棵状态树(state tree)
- 组件改变state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,于是state完成更新
- 组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件
- 其它组件可以通过订阅store中的状态(state)来刷新自己的视图
.
Redux架构原理:
- 剥离组件数据(state)
- 数据统一存放在store中
- 组件订阅store获得数据
- store推送数据更新
一句话总结:Redux统一保存了数据,在隔离了数据与UI的同时,负责处理数据的绑定。
Redux三大原则
-
单一数据源
-
Redux 应用中只有一个全局的、可观察的状态对象。这个状态对象通常被称为“store”。
-
整个应用的状态都存储在这个单一的 store 中,并且这个状态是只读的。
-
这意味着任何对状态的修改都必须通过发送 action 来触发 reducer 进行更新。
-
-
状态是只读的
-
Redux 中的 store 状态是只读的,这意味着不能直接修改存储在 store 中的状态。
-
当需要改变状态时,需要发送一个 action。这个 action 是一个描述发生了什么的普通 JavaScript 对象。
-
然后,Redux 会将 action 和当前的 state 传递给一个 reducer 函数。Reducer 函数接收当前的 state 和 action,然后返回一个新的 state。
-
新的 state 随后会被 Redux 保存到 store 中,并且这个新的 state 会自动触发所有订阅了 store 的组件进行更新。
-
-
使用纯函数来执行修改
-
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];
},
}
}
组件中使用
核心三步:
-
更新store:
store 通过dispatch
方法来通知reducer
执行一个动作(action
),以更新 store 中的数据store.dispatch(addLanguage({ name: '新的语言', code: 'new_lang' }));
-
订阅store
当 store 发生改变时,自动触发自定义方法storeChange
,以此更新 state 中的数据store.subscribe(this.storeChange.bind(this));
-
渲染View:
当store发生改变时,自动触发,得到最新的store,并更新到state中,从而重新渲染 ViewstoreChange() { 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应用程序中更加无缝和方便
特性/库 | Redux | react-redux |
---|---|---|
用途 | 独立的状态管理库 | React应用程序的Redux绑定库 |
与React的集成 | 需要手动集成 | 提供与React组件集成的功能 |
获取状态 | 通过 store.getState() 获取 | 使用 mapStateToProps 从props中获取 |
触发action | 通过 store.dispatch(action) 触发 | 使用 mapDispatchToProps 绑定actions到props上 |
连接组件与store | 手动在组件内部连接 | 使用 Provider 和 connect 函数连接 |
中间件支持 | 原生支持,如 Redux Thunk | 通过Redux原生支持 |
异步操作 | 需要使用中间件,如 Redux Thunk 或 Redux Saga | 依然需要Redux中间件,但react-redux提供了更好的与React组件的集成 |
适用场景 | 任何需要状态管理的JavaScript应用程序 | 特别是React应用程序,需要更好地组织、管理和更新状态 |
代码量 | 较少,专注于状态管理核心 | 稍多,因为需要与React组件集成 |
社区支持 | 广泛,有大量教程和库 | 活跃,有大量的React开发者使用 |
1. 安装
需要安装 redux
和 react-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 更新时,mapStateToProps
和 mapDispatchToProps
定义的函数会被调用,并更新组件的 props,从而触发组件的重新渲染
-
mapStateToProps(映射状态)
mapStateToProps 是一个函数,它接收Redux store的state作为参数,并返回一个对象,这个对象中的属性会作为props传递给组件(
language
、languageList
)。const mapStateToProps = (state) => { return{ language: state.language, languageList: state.languageList } };
或简写成(根据ES6简写):
const mapStateToProps = state => ({ language: state.language, languageList: state.languageList })
-
mapDispatchToProps(映射操作状态的方法)
mapDispatchToProps 也是一个函数,它接收dispatch函数作为参数,并返回一个对象,这个对象中的方法会作为props传递给组件(
changeLanguage
、addLanguage
),并且这些方法已经用dispatch进行了包装,可以直接调用以触发action。const mapDispatchToProps = (dispatch) => { return { changeLanguage: (val) => dispatch(changeLanguage(val)), addLanguage: () => dispatch(addLanguage({ name: '新的语言', code: 'new_lang' })), }; };
或简写成(根据API简写):
const mapDispatchToProps = { changeLanguage, // 自动dispatch addLanguage };
当把
changeLanguage
和addLanguage
action creators作为对象的属性直接赋值给mapDispatchToProps
时,react-redux 的 connect函数 会自动将它们与 Redux 的 dispatch函数 进行绑定,使得可以在组件的 props 中直接调用这些 action creators。
mapStateToProps
和 mapDispatchToProps
已经被定义并传递给了connect函数
,connect函数
将 Redux store
连接到组件,所以 LanguageComponent
会自动接收language
、languageList
、changeLanguage
和 addLanguage
作为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 函数合并起来,以管理整个应用的状态。
-
首先,如果定义多个独立的 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;
-
使用
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;
-
在 Redux store 中使用合并后的 reducer。
/src/redux/store.js
import { createStore } from 'redux'; import allReducer from './reducers'; // 引入合并后的 reducer const store = createStore(allReducer); export default store;
-
使用 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') );
-
在组件中连接 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);
注意:
-
每个 reducer 都应该是一个纯函数,即它不应该修改传入的参数,也不应该有任何副作用。
-
当
combineReducers
调用时,它会为每个 reducer 传入当前状态的对应 slice 和 action。
如果 action 不属于某个 reducer,那么该 reducer 应该返回其当前状态。 -
合并后的 reducer 产生的状态树的结构与传递给
combineReducers
的对象的结构相同。 -
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来触发状态的更新。
-
创建异步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)); }) };
-
使用
redux-thunk
:使用 Redux 的中间件
redux-thunk
,它允许在 Redux 的 action 创建函数中返回函数,而不是像通常那样返回一个 action 对象。这个特性使得可以进行异步操作,比如 API 调用,然后在这些操作完成后派发(dispatch)一个或多个 action。安装
redux-thunk
:-
npm install redux-thunk
-
yarn add redux-thunk
-
-
配置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;
-
在组件中调用异步Action:
import store from '../../redux/store'; import { addLanguageAsync } from '../../redux/action'; clickHandler = () => { store.dispatch(addLanguageAsync(val)); }
Redux DevTools 插件
-
浏览器中安装 redux-devtools 插件
-
项目中安装
redux-devtools-extension
yarn add redux-devtools-extension
-
项目中使用
在 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;
-
在浏览器中调试
浏览器中 redux-devtools 插件的图标已经点亮
浏览器的调试窗口出现
redux
选项卡,里面可以实时观测 store
打包部署
-
打包
yarn build
项目中将生成
build文件夹
-
全局安装
serve
yarn global add serve
npm i serve -g
-
启动
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> {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> {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;