使用React-Redux进行应用数据的管理
目前我们只在state中存放了一个数据focused,但一旦数据多起来考虑到react传递数据的特点,我们就必须借助React-Redux来帮助管理数据。
项目中安装React-Redux
redux是数据框架,而react-redux是第三方模块,他可以让我们在react中更方便的使用redux:
1 | //首先安装redux,再安装react-redux |
复习redux的工作流程:
- 首先我们需要有一个store,然后页面从store中取数据
- 如果页面想改变store中的数据,他需要:
- 派发一个action给store
- store将之前的数据和action一起给reducer
- reducer结合action和之前的数据返回一个新的state给store
- store拿到新的数据后替换自身的state,告诉页面自己更新了
- 页面的state也更新,页面重新渲染
使用redux和react-redux
- 创建store:src下创建文件夹store,创建index.js文件,在该文件中创建store实例
- 注意:在创建store时就要将reducer传给store,否则store不会处理数据。
- 创建reducer:在文件夹store中创建reducer.js文件用于放置reducer。
- reducer.js中导出的是一个纯函数(给定固定输入就有固定输出,且不能改变参数值。涉及异步/时间的就不是纯函数)
- 导出的函数接受两个参数,参数1
state
其实就是store的state,参数2是action
. - 导出的函数返回一个state(默认值)/newState(修改后的state)给store。(注意newState是深拷贝出来的)
- 在项目的根组件(入口组件)App.js中,引入store,并使用react-redux中的Provider组件,使所有组件都有能力连接store。
- Header组件中,使用react-redux的connect方法连接store,原本我们导出的是Header组件,现在改成导出“使用
connect(mapStateToProps,mapDispatchToProps)()
生成的组件”。 - reducer.js中,将Header组件的state中数据放到reducer的defaultState中作为store的默认数据。
- 在Header组件中想要使用
this.props.数据名
从store中获取原本放在state中的数据,此时需要先使用connect方法的参数1mapStateToProps()
进行store中的数据与props的映射(将store中获取到的数据绑在props上),才能使用this.props.数据名
拿到store中的数据。- mapStateToProps()接受的参数state是store的state。
- 处理事件绑定函数,原本两个绑定函数中都是处理state中数据的,现在我们需要将这两个函数挂在props上(即放在connect的参数2mapDispatchToProps()内定义),新建action并通过dispatch将action传给store,store将action和state传给reducer,在reducer中添加判断,通过action的type决定给store的state返回值。
创建store
src-store-index.js创建store:
1 | import { createStore } from "redux"; |
创建reducer
2.src-store-index.js创建reducer:
1 | const defaultState = {}; |
引入store,使用Provider组件
App.js中 引入store,并使用react-redux中的Provider组件使所有组件都有能力连接store:
1 | import React, { Component } from 'react'; |
(注意:一般我们将组件的引用放在前面,样式的引用放在后面,)
使用connect方法连接store
common-header-index.js中,使用react-redux的connect方法连接store,原本我们导出的是Header组件,现在改成导出“使用connect()生成的组件”:
1 | import { connect } from "react-redux"; |
1 | const mapStateToProps = (state) => { |
观察页面正常就可以继续写。
将数据放入store中(reducer)
reducer.js中,将Header组件的state中数据放到reducer的defaultState中作为store的默认数据:
1 | const defaultState = { |
组件从store中取值
- 先使用connect方法的参数1
mapStateToProps()
进行store中的数据与props的映射,才能使用props拿到store中的数据。(mapStateToProps()接受的参数state是store的state) - 在Header组件中使用props从store中获取原本放在state中的数据focused。
common-header-index.js:
先修改mapStateToProps(),将focused绑在props上:
1 | const mapStateToProps = (state) => { |
再将this.state.focused
改成this.props.focused
即可获取store中的数据focused
此时绑定函数还没迁移,所以在页面上我们只能通过手动改true和false来看效果。
处理事件绑定函数
- 原本两个绑定函数中都是处理state中数据的。
- 现在我们需要:
- 将这两个函数挂在props上(即放在connect的参数2mapDispatchToProps()内定义)
- 新建action并通过dispatch将action传给store,store将action和state传给reducer
- 在reducer中添加判断,通过action的type决定给store的state返回值。
- 注意:两个函数中间使用逗号分隔
common-header-index.js:
将这两个函数挂在props上(即放在connect的参数2mapDispatchToProps()内定义),新建action并通过dispatch将action传给store,store将action和state传给reducer:
1 | const mapDispatchProps = (dispatch) => { |
调用时从props上获取:
1 | <NavSearch |
store-reducer.js:
在reducer中添加判断,通过action的type决定给store的state返回值
1 | const defaultState = { |
页面动画效果也实现好了。
处理无状态组件
- 此时Header组件只有render函数,所以可成为无状态组件,为提升性能,我们可以将其转换为无状态组件。
- 转换步骤:
- 直接让Header组件等于一个箭头函数(
const Header = (props) => {}
) - 将原本render函数中的内容复制到箭头函数中return出去(不返回则外部无法获取,因为调用Header组件相当于调用它返回的东西)
- 注意该函数接收父级的props作为参数,所以所有
this.props.xxx
都改成props.xxx
- 直接让Header组件等于一个箭头函数(
header-index.js:
1 | const Header = (props) => { |
使用combineReducers对数据拆分管理
- 虽然我们已经使用redux和react-redux进行数据管理,但页面上的开发者工具Redux DevTools还是灰色的,也就是说F12中的redux测试是不好用的:
- 想要使用Redux DevTools就需要在项目的
store-index.js
中进行配置。
配置开发者工具REDUX_DEVTOOLS_EXTENSION
github中搜索redux-devtools-extension
,在文档中查看他的高级用法Advanced store setup
配置成功:(展示 REDUX_DEVTOOLS_EXTENSION
的具体用法)
为什么使用combineReducers拆分数据
- 如果一个reducer中存放过多数据,就会导致后期代码维护十分困难。(一般一个文件超过300行代码就是没设计好)
- 所以我们可以将一个reducer拆分成很多个小的reducer,最终再整合起来。
- redux提供了一个combineReducers模块用于将小的reducer们整合成大的一个reducer
combineReducers使用步骤
- header文件夹下=>新建store文件夹=>新建reducer.js文件,用于存储和Header组件相关的store数据/操作。
- 在src-store-reducer.js中,将分开的小reducer通过 combineReducers 结合成完整的一个reducer。(键名是自定义的,用于在组件中获取store中的数据)
- 由于原本放在reducer中的数据迁移到了小reducer中,所以**在组件中规定 store映射到props 的函数(connect方法的参数2
mapStateToProps()
)中的state.focused
也应当改为state.header.focused
**,组件才能获取到store中的数据。 - 优化:在 总reducer 中引入 分reducer 的路径太长,此时可以在 分reducer 的文件夹中新建index.js用于 “引入、输出 分reducer”,这样在 总reducer 中引入 分reducer 时就可以直接从这个index中引入了。
1.header文件夹下=>新建store文件夹=>新建reducer.js文件,用于存储和Header组件相关的store数据(即原本store中reducer的内容):
1 | const defaultState = { |
2.在src-store-reducer.js中,将分开的小reducer通过 combineReducers 结合成完整的一个reducer:
1 | import { combineReducers } from "redux"; |
此时可以看到focused不是在顶层state中了,她在header(也就是我们使用combineReducers时规定的键中)下,focused值会变,但动画效果不生效:
3.由于原本放在reducer中的数据迁移到了小reducer中,所以**在组件中规定store和props的映射的函数(connect方法的参数2mapStateToProps()
)中的state.focused
也应当改为state.header.focused
**,组件才能获取到store中的数据:
1 | const mapStateToProps = (state) => { |
4.优化:在 总reducer 中引入 分reducer 的路径太长,此时可以在 分reducer 的文件夹中新建index.js用于 “引入、输出 分reducer”,这样在 总reducer 中引入 分reducer 时就可以直接从这个index中引入了。
common-header-store新建index.js:
1 | import reducer from "./reducer"; |
src-store-reducer.js中引用路径就变短(**as
是ES6的语法,用于起别名。**否则两个reducer会报错):
1 | import { combineReducers } from "redux"; |