- A+
React + Redux
在recat中不使用redux 时遇到的问题
在react中组件通信的数据是单向的,顶层组件可以通过props属性向下层组件传递数据,而下层组件不能向上层组件传递数据,要实现下层组件修改数据,需要上层组传递修改数据的方法到下层组件,当项目越来越的时候,组件之间传递数据变得越来越困难
在react中加入redux 的好处
使用redux管理数据,由于Store独立于组件,使得数据管理独立于组件,解决了组件之间传递数据困难的问题
使用redux
下载redux
npm install redux react-redux
redux 工作流程
- 组件通过 dispatch 触发action
- store 接受 action 并将 action 分发给 reducer
- reducer 根据 action 类型对状态进行更改并将更改后的数据返回给store
- 组件订阅了store中的状态,store中的状态更新会同步到组件
使用react+redux实现计数器
- 创建项目,并安装 redux
# 如果没有安装react脚手架则执行这条命令安装reate脚手架 npm install -g create-react-app # 创建reate项目 create-react-app 项目名 # 进入项目 cd 项目名 # 安装 redux npm install redux reate-redux
- 引入redux,并根据开始实现的代码在react中实现计数器
//index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; 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 Count() { return <div> <button onClick={() => store.dispatch(increment)}>+</button> <span>{store.getState().count}</span> <button onClick={() => store.dispatch(decrement)}>-</button> </div> } store.subscribe( () => { console.log(store.getState()) ReactDOM.render( <React.StrictMode> <Count /> </React.StrictMode>, document.getElementById('root') ); }) ReactDOM.render( <React.StrictMode> <Count /> </React.StrictMode>, document.getElementById('root') );
明显以上方式虽然可以实现计数器的功能,但在实际项目中肯定不能这样使用,因为组件一般都在单独的文件中的,这种方式明显在其他组件中并不能获取到Store。
计数器案例代码优化-让store全局可访问
为了解决Store获取问题需要使用react-redux来解决这个问题,react-redux给我们提供了Provider组件和connect方法
- Provide 组件
是一个组件 可以吧创建出来的store 放在一个全局的地方,让组件可以拿到store,通过provider组件,将 store 放在了全局的组件可以够的到的地方 ,provider要求我们放在最外层组件
- connect
connect 帮助我们订阅store中的状态,状态发生改变后帮助我们重新渲染组件
通过 connect 方法我们可以拿到 store 中的状态 把 store 中的状态映射到props中
通过 connect 方法可以拿到 dispatch 方法
connect 的参数为一个函数 这个函数可以拿到store中的状态,要求我们这个函数必须返回一个对象,在这个对象中写的内容都会映射给组件的props属性
connect 调用后返回一个函数 返回的这个函数继续调用需要传入组件告诉connect需要映射到那个组件的props
- 新建 Component 文件夹、创建 Count.js 文件
import React from 'react' function Count() { return <div> <button onClick={() => store.dispatch(increment)}>+</button> <span>{store.getState().count}</span> <button onClick={() => store.dispatch(decrement)}>-</button> </div> } export default Count
- 引入 Provider 组件放置在最外层,并制定store
ReactDOM.render( // 通过provider组件 将 store 放在了全局的组件可以够的到的地方 provider要求我们放在最外层组件 <Provider store={store}><Count /></Provider>, document.getElementById('root') );
- 引入 connect 方法 根据 connect 的使用来包裹组件
const mapStateProps = state => ({ count: state.count, a: '1' }) // connect 的参数为一个函数 这个函数可以拿到store中的状态,要求我们这个函数必须返回一个对象,在这个对象中写的内容都会映射给组件的props属性 // connect 调用后返回一个函数 返回的这个函数继续调用需要传入组件告诉connect需要映射到那个组件的props export default connect(mapStateProps)(Count)
- 改造 Count 组件把 action 复制到该文件中
const increment = { type: 'increment' } const decrement = { type: 'decrement' } function Count({count,dispatch}) { return <div> <button onClick={() => {dispatch(increment)}}>+</button> <span>{count}</span> <button onClick={() => {dispatch(decrement)}}>-</button> </div> }
现在项目已经可以运行了但是Count组件中的 提交Action的那一长串代码影响视图的可读性,所以代码还是需要优化
计数器案例代码优化-让视图中的代码可读性更高
我们希望视图中直接调用一个函数这样视图代码可读性强,这个需要利用connect的第二个参数,第二个参数是一个函数,这个函数的形参就是dispatch方法,要求这个函数返回一个对象,返回的这个对象中的内容都会映射到组件的props属性上
- 申明一个变量为connect中的第二个参数,在这个变量中返回执行不同action操作的对象
// connect 的第二个参数 这个参数是个函数 这个函数的形参就是dispatch方法 要求返回一个对象 这个对象中的属性会被映射到组件的props上 const mapDispatchToProps = dispatch => ({ increment (){ dispatch({ type: 'increment' }) }, decrement (){ dispatch({ type: 'decrement' }) } }) // connect 的参数为一个函数 这个函数可以拿到store中的状态,要求我们这个函数必须返回一个对象,在这个对象中写的内容都会映射给组件的props属性 // connect 调用后返回一个函数 返回的这个函数继续调用需要传入组件告诉connect需要映射到那个组件的props export default connect(mapStateProps, mapDispatchToProps)(Count)
- 在组件中结构props在视图中直接绑定事件
function Count({count,increment,decrement}) { return <div> <button onClick={increment}>+</button> <span>{count}</span> <button onClick={decrement}>-</button> </div> }
通过这次优化我们发现 调用 dispatch 触发action 的方法的代码都是重复的,所以还需要继续优化
优化调用 dispatch 触发action 的方法的重复代码简化
利用 bindActionCreators 来简化 dispatch 触发 action的操作,bindActionCreators来帮助我们生成执行action动作的函数
bindActionCreators 有两个参数,第一个参数为 执行action的对象,第二个参数为 dispatch方法
- 分离action操作,新建store/actions/counter.actions.js文件把执行action操作单独放在这个文件并导出
export const increment = () => ({type: 'increment'}) export const decrement = () => ({type: 'decrement'})
- 在Count.js中导入关于计数器的action,用bindActionCreators方法来生成dispatch执行action函数
import { bindActionCreators } from 'redux' import * as counterActions from './../store/actions/counter.actions' const mapDispatchToProps = dispatch => (bindActionCreators(counterActions, dispatch)) // connect 的参数为一个函数 这个函数可以拿到store中的状态,要求我们这个函数必须返回一个对象,在这个对象中写的内容都会映射给组件的props属性 // connect 调用后返回一个函数 返回的这个函数继续调用需要传入组件告诉connect需要映射到那个组件的props export default connect(mapStateProps, mapDispatchToProps)(Count)
代码优化到这里我们发现,redux的代码与组件融合在一起,所以我需要拆分成独立的,为什么要抽离redux呢?因为我们要让我们的代码结构更加合理
重构计数器,把redux相关代码抽离
把reducer函数抽离为单独的文件、把创建store抽离到单独的文件中
- 因为在reducer 和 actions中我们都写了字符串,但是字符串没有提示所以我们把字符串定义成常量防止我们出现单词错误这种低级错误,新建 src/store/const/counter.const.js 文件
export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
- 新建 src/store/reducers/counter.reducers.js 文件把 reducer 函数抽离到此文件中
import { INCREMENT, DECREMENT} from './../const/counter.const' const initialState = { count: 0 } // eslint-disable-next-line import/no-anonymous-default-export export default (state = initialState, action) => { switch (action.type) { case INCREMENT: return { count: state.count + 1 } case DECREMENT: return { count: state.count - 1 } default: return state } }
- 更改actions中的字符串为引入变量
import { INCREMENT, DECREMENT} from './../const/counter.const' export const increment = () => ({type: INCREMENT}) export const decrement = () => ({type: DECREMENT})
- 创建src/store/index.js文件 ,在这个文件中创建store 并导出
import { createStore } from 'redux'; import reducer from './reducers/counter.reducers' export const store = createStore(reducer)
- 在引入store的文件中改变为冲项目中store文件中引入store
import React from 'react'; import ReactDOM from 'react-dom'; import Count from './components/Count'; import { store } from './store' import { Provider } from 'react-redux' /** * react-redux 让react 和 redux 完美结合 * Provider 是一个组件 可以吧创建出来的store 放在一个全局的地方 让组件可以拿到store * connect 是一个方法 */ ReactDOM.render( // 通过provider组件 将 store 放在了全局的组件可以够的到的地方 provider要求我们放在最外层组件 <Provider store={store}><Count /></Provider>, document.getElementById('root') );
为action 传递参数,对计数器案例做扩展
这个计数器案例已经实现了点击按钮加一减一操作了,现在有个新需求我们需要加减一个数值例如加五减五
这就需要对action传递参数了
- 在视图中按钮绑定函数传入参数
function Count({count,increment,decrement}) { return <div> <button onClick={() => increment(5)}>+</button> <span>{count}</span> <button onClick={() => decrement(5)}>-</button> </div> }
- 在dispacth执行action动作时接受参数并传入到action中
export const increment = payload => ({type: INCREMENT, payload}) export const decrement = payload => ({type: DECREMENT, payload})
- 在reducers中接收参数并作相应处理
export default (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 } }