在 React 中使用 Redux 解决的问题
在 React 项目中未加入 Redux 时的问题
在 React 中组件通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件传递数据,而下层组件不能直接向上层组件传递数据。要实现下层组件修改上层组件的数据,需要上层组件传递修改数据的方法到下层组件。当项目越来越大的时候,组件之间传递数据以及传递修改数据的方法变得越来越困难。
在 React 项目中加入 Redux 的好处
使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。
React + Redux 安装 Redux
在 react 项目中使用 redux 要下载两个模块
npm install redux react-redux
React 中 Redux 的工作流程
在 React 中 Redux 的工作流程有些变化:
- 组件通过 dispatch 方法触发 Action
- Store 接收 Action 并将 Action 分发给 Reducer
- Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
- 组件订阅了 Store 中的状态,Store 中的状态更新会同步到组件
React 计数器案例
React 实现
创建项目安装模块
# 创建项目(React 17 版本) npx create-react-app myapp # 安装 redux cd myapp npm install redux
删掉无用的文件
├─ src │ ├─ App.css │ ├─ App.test.js │ ├─ index.css │ ├─ logo.svg │ ├─ reportWebVitals.js │ └─ setupTests.js
初步实现
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux' const initialState = { count: 0 } 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 } } const store = createStore(reducer) const increment = { type: 'increment' } const decrement = { type: 'decrement' } function Counter() { return{store.getState().count}} store.subscribe(() => { ReactDOM.render(, document.getElementById('root') ); }) console.log(store.getState()) ReactDOM.render( , document.getElementById('root') );
使用 Redux
开发时我们会把组件写在单独的文件中,如果将 Counter 组件单独提取,就无法访问 store 对象以及一些其它的问题,所以需要使用 redux。
安装 react-redux
npm install react-redux
react-redux 用于让 react 和 redux 完美结合,它仅仅提供两个内容:
- Provider 组件
- 它可以将创建出来的 store 放到全局,允许所有组件访问
- 可以解决将组件存储在单独文件中时,访问 store
- connect 方法
- connect 方法会帮助订阅 store,当 store 中的状态发生变化,会重新渲染组件
- 解决了需要手动订阅更新组件状态
- connect 方法可以获取 store 中的状态,映射到组件的 props 属性中
- connect 方法可以获取 dispatch 方法,组件可以通过 props.dispatch访问
Provide 组件
- Provider 组件要包裹项目中所有的组件,也就是应该被放在最外层组件上
- Provider 通过 store 属性接收创建的 store 对象
connect 方法
- 首先调用 connect 方法
- 第一个参数是一个函数
- 函数接收的第一个参数,即组件中的状态 state
- 函数要返回一个对象,该对象定义的属性都会映射到组件的 props 属性中
- 第二个参数也是一个函数
- 函数接收的第一个参数,即 dispatch 方法
- 函数要返回一个对象,可以定义触发 Action 的方法,它们同样会被映射到组件的 props 属性中
- 返回一个方法
- 第一个参数是一个函数
- 继续调用返回的方法,并传递当前的组件
修改代码
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux' import Counter from './components/Counter' import { Provider } from 'react-redux' const initialState = { count: 0 } 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 } } const store = createStore(reducer) ReactDOM.render( // 通过 provider 组件将 store 放在了全局,供所有组件可以访问, document.getElementById('root') );
提取的 Counter 组件
// src/components/Counter.js import { connect } from 'react-redux' function Counter(props) { return{props.count}} const mapStateToProps = state => ({ count: state.count }) export default connect(mapStateToProps)(Counter)
使用 connect 第二个参数简化组件
// src/components/Counter.js import { connect } from 'react-redux' function Counter({count, increment, decrement}) { return{count}} const mapStateToProps = state => ({ count: state.count }) const mapDispatchToProps = dispatch => ({ increment () { dispatch({ type: 'increment' }) }, decrement () { dispatch({ type: 'decrement' }) } }) export default connect(mapStateToProps, mapDispatchToProps)(Counter)
使用 bindActionCreators 方法继续简化
触发 Action 的方法 increment
和 decrement
的内容基本是一样的,redux 提供 bindActionCreators 方法生成一个函数,来简化这种重复性代码。
- 参数
- actionCreators: 对象或返回对象的函数
- key是生成函数的名称
- value是一个返回 Action 的函数
- dispatch: 需传递Store 的 dispatch 方法
- actionCreators: 对象或返回对象的函数
- 返回一个对象,同 connect 接收的第二个参数
// src/components/Counter.js import { connect } from 'react-redux' import { bindActionCreators } from 'redux' function Counter({count, increment, decrement}) { return{count}} const mapStateToProps = state => ({ count: state.count }) const mapDispatchToProps = dispatch => ({ ...bindActionCreators({ increment() { return { type: 'increment' } }, decrement() { return { type: 'decrement' } } }, dispatch) }) export default connect(mapStateToProps, mapDispatchToProps)(Counter)
此时还没有达到简化的效果,可以将 actionCreators 提取到单独的文件中。
// src\store\actions\counter.action.js export const increment = () => ({ type: 'increment' }) export const decrement = () => ({ type: 'decrement' })
// src/components/Counter.js import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import * as counterActions from '../store/actions/counter.action' function Counter({count, increment, decrement}) { return{count}} const mapStateToProps = state => ({ count: state.count }) const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch) export default connect(mapStateToProps, mapDispatchToProps)(Counter)
代码重构
为了继续演示 Redux 的相关内容,将与 Redux 相关的代码从 src/index.js
中抽离出去,让项目结构更加合理。
- 将 reducer 函数抽离到一个文件中
- 将创建 store 的代码手里到一个文件中
- 将 Actions 的 type 抽象成模块中的成员,好处是:
编辑器有提示,避免写错
编辑器自动插入模块引入的代码
将 Actions 的 type 抽象成模块中的成员
// src\store\actions\counter.action.js import { DECREMENT, INCREMENT } from "../const/counter.const" export const increment = () => ({ type: INCREMENT }) export const decrement = () => ({ type: DECREMENT })
将 reducer 函数抽离到一个文件中
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from "../const/counter.const" const initialState = { count: 0 } 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 } } export default reducer
将创建 store 的代码手里到一个文件中
// src\store\index.js import { createStore } from 'redux' import reducer from './reducers/counter.reducer' export const store = createStore(reducer)
修改后的 src/index.js
// src/index.js import React from 'react' import ReactDOM from 'react-dom' import Counter from './components/Counter' import { Provider } from 'react-redux' import { store } from './store' ReactDOM.render( // 通过 provider 组件将 store 放在了全局,供所有组件可以访问, document.getElementById('root') )
为 Action 传递参数
当前计数器对数字进行递增/减的数值为 1,现在通过给 Action 传递参数,允许自定义数值。
- 传递参数:调用触发 Action 函数的时候传递参数
- 接收参数:返回 Action 的函数接收参数,添加到返回的 Action 对象中
- 处理参数:reducer 函数处理的时候,可以从 action 对象获取这个参数,进行处理
传递参数:
// src/components/Counter.js function Counter({ count, increment, decrement }) { // 修改 Counter 组件中调用 increment decrement 函数的地方 return ({count}) }
接收参数:
// src\store\actions\counter.action.js import { DECREMENT, INCREMENT } from "../const/counter.const" export const increment = payload => ({ type: INCREMENT, payload }) export const decrement = payload => ({ type: DECREMENT, payload })
处理参数:
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from "../const/counter.const" const initialState = { count: 0 } 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 } } export default reducer
Redux 弹出框
在页面中显示两个按钮:
- 显示:显示弹出框
- 隐藏:隐藏弹出框
初始化静态内容
Modal 组件
// src\components\Modal.js function Modal() { const styles = { width: 200, height: 200, position: 'absolute', left: '50%', top: '50%', marginLeft: -100, marginTop: -100, backgroundColor: 'skyblue' } return () } export default Modal
修改 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 放在了全局,供所有组件可以访问, document.getElementById('root') )
修改 src/App.js
// src\App.js import Counter from './components/Counter' import Modal from './components/Modal' function App() { return () } export default App
添加默认隐藏状态
在 reducer 中添加显示状态的属性
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from "../const/counter.const" const initialState = { count: 0, showStatus: false } function reducer(state = initialState, action) {...} export default reducer
在组件中使用状态
// src\components\Modal.js import { connect } from 'react-redux' function Modal({ showStatus }) { const styles = { width: 200, height: 200, position: 'absolute', left: '50%', top: '50%', marginLeft: -100, marginTop: -100, backgroundColor: 'skyblue', display: showStatus ? 'block' : 'none' } return () } const mapStateToProps = state => ({ showStatus: state.showStatus }) export default connect(mapStateToProps)(Modal)
定义操作按钮
定义 Action 的 type
// src\store\const\modal.const.js export const SHOWMODAL = 'showModal' export const HIDEMODAL = 'hideModal'
定义生成 Action 的函数
// src\store\actions\modal.actions.js import { HIDEMODAL, SHOWMODAL } from "../const/modal.const" export const show = () => ({ type: SHOWMODAL }) export const hide = () => ({ type: HIDEMODAL })
创建触发 Action 的方法并使用
// src\components\Modal.js import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import * as modalActions from '../store/actions/modal.actions' function Modal({ showStatus, show, hide }) { const styles = { width: 200, height: 200, position: 'absolute', left: '50%', top: '50%', marginLeft: -100, marginTop: -100, backgroundColor: 'skyblue', display: showStatus ? 'block' : 'none' } return () } const mapStateToProps = state => ({ showStatus: state.showStatus }) const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch) export default connect(mapStateToProps, mapDispatchToProps)(Modal)
修改 reducer 函数,处理变更:
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from '../const/counter.const' import { HIDEMODAL, SHOWMODAL } from '../const/modal.const' const initialState = { count: 0, showStatus: false } function reducer(state = initialState, action) { switch (action.type) { case INCREMENT: // reducer 返回的对象会替换 store 中的状态对象,所以要将其它状态也包含进去 return { ...state, count: state.count + action.payload } case DECREMENT: return { ...state, count: state.count - action.payload } case SHOWMODAL: return { ...state, showStatus: true } case HIDEMODAL: return { ...state, showStatus: false } default: return state } } export default reducer
注意:reducer 返回的对象会替换 store 中的状态对象,所以要将其它状态也包含进去
衍生的问题
在 reducer 函数中匹配了所有状态的变更,当项目越来越大,状态越来越多时,管理起来就很麻烦。
所以要将 rreducer 函数进行拆分。
拆分合并 reducer 拆分
将 modal 拆分出去
// src\store\reducers\modal.reducer.js import { HIDEMODAL, SHOWMODAL } from '../const/modal.const' const initialState = { show: false } const reducer = (state = initialState, action) => { switch (action.type) { case SHOWMODAL: return { ...state, showStatus: true } case HIDEMODAL: return { ...state, showStatus: false } default: return state } } export default reducer
// src\store\reducers\counter.reducer.js import { DECREMENT, INCREMENT } from '../const/counter.const' const initialState = { count: 0, } function reducer(state = initialState, action) { switch (action.type) { case INCREMENT: // reducer 返回的对象会替换 store 中的状态对象,所以要将其它状态也包含进去 return { ...state, count: state.count + action.payload } case DECREMENT: return { ...state, count: state.count - action.payload } default: return state } } export default reducer
合并
合并 reducer 需要借助 redux 提供的 combineReducers
方法。
combineReducers 把一个由多个不同 reducer 函数作为 value 的 object 对象,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。
由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按传递给 combineReducers() 时对应的 key 进行命名。
// src\store\reducers\root.reducer.js import { combineReducers } from 'redux' import CounterReducer from './counter.reducer' import ModalReducer from './modal.reducer' // 合并后的 store 为 { counter: { count: 0 }, modal: { showStatus: false } } export default combineReducers({ counter: CounterReducer, modal: ModalReducer })
// src\store\index.js import { createStore } from 'redux' import RootReducer from './reducers/root.reducer' export const store = createStore(RootReducer)
调整组件
因为 store 中的数据结构发生变化,所以还需要调整下组件中获取状态的地方
// src\store\index.js import { createStore } from 'redux' import RootReducer from './reducers/root.reducer' export const store = createStore(RootReducer)
// src\components\Modal.js const mapStateToProps = state => ({ showStatus: state.modal.showStatus })
到此这篇关于在 React 中使用 Redux 解决的问题的文章就介绍到这了,更多相关React Redux 案例内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!