React 06. redux
状态管理模式1、redux
概述
2、基本语法
3、结构化拆分
4、模块化拆分
5、解耦合
6、总结
redux
Redux
是JavaScript
状态容器,提供可预测化的状态管理,可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验
Redux
除了和 React 一起用外,还支持其它界面库。 它体小精悍(只有2kB
,包括依赖)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
小总结:什么是
redux
redux
它是一个独立的javascript
状态管理模式,可以应用于客户端应用、服务端应用或者原生应用等不同环境,易于测试,提高了数据管理的开发效率!
redux
是如何和react
结合起来,完成数据的状态管理的?
react
组件,需要数据时,可以通过redux store
固定函数store.getState()
获取数据react
组件,需要操作数据时,可以创建一个actionType
,如增加品牌数据可以是"brand/add"
actionCreator
,如{type: 'brand/add', data:数据}
reducer
合并函数,执行函数并自动同步数据,将数据同步到state
,页面组件中重新获取到更新后的数据创建项目
$ npx create-react-app app01
项目降级
$ npm i react@17 react-dom@17 --force -S
修改启动文件:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<App/>,
document.querySelector("#root")
)
store
安装依赖模块
$ npm i redux -S
创建状态管理模块:src/store/index.js
// 1、引入依赖的模块
import { legacy_createStore as createStore} from 'redux'
// 2、构建初始数据
// 以后正式开发,axios从数据接口进行获取
const initData = [
{id: 2, bname: '华为', bprice: 8999},
{id: 1, bname: '小米', bprice: 5999}
]
// 3、构建reducer合并函数
// 参数1:state数据,必须初始化
// 参数2:actionCreator操作模式,如{type: 'brand/add', data: {id, bname, bprice}}
function brandReducer(state=initData, action) {
switch(action.type) {
case 'brand/add': // 增加数据,操作类型actionType:"brand/add"
break;
case 'brand/edit': // 编辑数据
break;
case 'brand/del': // 删除数据
break;
default: // 默认获取数据
return state
}
}
// 4、创建store对象
const store = createStore(brandReducer)
// 5、导出store
export default store
注意:上面的store
状态管理模式,不需要在主模块中导入;后续使用过程中那个react
组件需要管理数据就在当前组件中直接导入使用即可!
编辑src/views/Brand/index.jsx
,读取并渲染展示数据
import React, { Component } from 'react'
import store from '../../store'
import './Brand.css'
export default class index extends Component {
render() {
// 打印展示数据
// console.log(store.getState())
return (
品牌数据管理
序号
名称
单价
操作
{
// 从store中 读取并展示数据
store.getState().map(item => (
{item.id}
{item.bname}
{item.bprice}
))
}
)
}
}
编辑src/views/Brand/index.jsx
,设置文本输入框,添加品牌数据
import React, { Component } from 'react'
import store from '../../store'
import './Brand.css'
export default class index extends Component {
// 1. >>>>>>>>> 添加数据的 状态绑定变量
state = {
brandInfo: ''
}
// 2. >>>>>>>>> 添加数据的操作函数,发起disaptch操作
addBrand() {
if(this.state.brandInfo.trim().length <= 0){
alert("输入的数据不能为空")
return
}
// 拆分解构数据
let [bname, bprice] = this.state.brandInfo.split(',')
// store.dispatch() 发起操作行为
// 参数:actionCreator
// dispatch()函数执行,会自动调用reducer函数完成数据处理
store.dispatch({type: 'brand/add', data: {bname, bprice}})
}
// 3. >>>>>>>>>>>> 通知更新,一旦state中数据发生更新,通知页面刷新
componentDidMount() {
// 接收redux store通知更新
store.subscribe( () => {
// 一旦state数据发生更新,自动刷新页面,同时将brandInfo置空
this.setState({
brandInfo: ''
})
})
}
render() {
// 打印展示数据
// console.log(store.getState())
return (
品牌数据管理
{/* 添加页面视图解构:增加品牌数据,输入内容,点击按钮完成添加 */}
this.setState({brandInfo: e.target.value})}/>
序号
名称
单价
操作
{
// 从store中 读取并展示数据
store.getState().map(item => (
{item.id}
{item.bname}
{item.bprice}
))
}
)
}
}
改造状态管理 模块:src/store/index.jsx
,完成了添加品牌数据的操作
// 1、引入依赖的模块
import { legacy_createStore as createStore} from 'redux'
// 2、构建初始数据
// 以后正式开发,axios从数据接口进行获取
const initData = [
{id: 2, bname: '华为', bprice: 8999},
{id: 1, bname: '小米', bprice: 5999}
]
// 3、构建reducer合并函数
// 参数1:state数据,必须初始化
// 参数2:actionCreator操作模式,如{type: 'brand/add', data: {id, bname, bprice}}
function brandReducer(state=initData, action) {
switch(action.type) {
case 'brand/add': // 增加数据,操作类型actionType:"brand/add"
// 操作数据的时候,尽量避免直接操作state内部数据(操作规范)
// 从actionCreator中解构获取要添加的数据
let {bname, bprice} = action.data
let id = state.length > 0 ? state[0].id+1:1
// 添加数据的时候,不要直接操作state(state.unshift() / state.push() X)
// 将新数据和拆分的state数据,组成新数据进行返回,自动覆盖state原有数据
return [{id, bname, bprice}, ...state]
case 'brand/edit': // 编辑数据
break;
case 'brand/del': // 删除数据
break;
default: // 默认获取数据
return state
}
}
// 4、创建store对象
const store = createStore(brandReducer)
// 5、导出store
export default store
面试的问题,主要集中在JS
和框架
1、原生
js
中jsonp
跨域的原理?2、原生
js
中Map
类型和Set
类型的区别?3、原生
js
中数组的map()
函数和forEach()
函数的区别?4、是如何理解原生
js
中闭包函数?5、
watch
和computed
区别?6、
Vue
数据绑定,普通变量绑定和数组绑定区别?数组是如何绑定的?7、
element
中表格展示数据,页面跳转后回到当前页面,如何保持上一次选择状态?8、如何理解原生
js
中原型链?9、
Vue
生命周期都有哪些?父子组件嵌套生命周期执行顺序?10、
elementui
和其他的一些框架vant、layui、lve
的区别?11、介绍一下原生
js
中的BOM
?12、介绍一下原生
js
中获取节点的函数?13、介绍一下
ES6
?14、手写一下原生
JS
中的ajax
操作步骤?15、说明一下普通函数和箭头函数的区别?
编写src/views/Brand/index.jsx
import React, { Component } from 'react'
import store from '../../store'
import './Brand.css'
export default class index extends Component {
state = {
brandId: '',
brandInfo: ''
}
delBrand(brand) {
const result = window.confirm("确定要删除该数据吗?")
if (!result) return
// dipatch()发送删除数据的请求
// 参数:actionCreator
store.dispatch({ type: 'brand/del', data: { id: brand.id } })
}
editBrand(brand) {
console.log("editBrand", brand)
this.setState({
brandId: brand.id,
brandInfo: brand.bname + "," + brand.bprice
})
}
addBrand() {
if (this.state.brandInfo.trim().length <= 0) {
alert("输入的数据不能为空")
return
}
// 拆分解构数据
let [bname, bprice] = this.state.brandInfo.split(',')
if (this.state.brandId) {
// 编辑
// dispatch()发起了一个更新actionCreator操作
store.dispatch({type: 'brand/edit', data:{id: this.state.brandId, bname, bprice}})
} else {
// 新增
// store.dispatch() 发起操作行为
// 参数:actionCreator
// dispatch()函数执行,会自动调用reducer函数完成数据处理
store.dispatch({ type: 'brand/add', data: { bname, bprice } })
}
}
componentDidMount() {
// 接收redux store通知更新
store.subscribe(() => {
// 一旦state数据发生更新,自动刷新页面,同时将brandInfo置空
this.setState({
brandInfo: '',
brandId: ''
})
})
}
render() {
// 打印展示数据
// console.log(store.getState())
return (
品牌数据管理
this.setState({ brandInfo: e.target.value })} />
序号
名称
单价
操作
{
// 从store中 读取并展示数据
store.getState().map(item => (
{item.id}
{item.bname}
{item.bprice}
))
}
)
}
}
编辑src/store/index.js
,添加编辑和删除reducer函数
功能
// 1、引入依赖的模块
import { legacy_createStore as createStore} from 'redux'
// import { createStore } from 'redux'
// 2、构建初始数据
// 以后正式开发,axios从数据接口进行获取
const initData = [
{id: 2, bname: '华为', bprice: 8999},
{id: 1, bname: '小米', bprice: 5999}
]
// 3、构建reducer合并函数
// 参数1:state数据,必须初始化
// 参数2:actionCreator操作模式,如{type: 'brand/add', data: {id, bname, bprice}}
function brandReducer(state=initData, action) {
switch(action.type) {
case 'brand/add': // 增加数据,操作类型actionType:"brand/add"
// // 操作数据的时候,尽量避免直接操作state内部数据(操作规范)
// // 从actionCreator中解构获取要添加的数据
let {bname, bprice} = action.data
let id = state.length > 0 ? state[0].id+1:1
// // 添加数据的时候,不要直接操作state(state.unshift() / state.push() X)
// // 将新数据和拆分的state数据,组成新数据进行返回,自动覆盖state原有数据
return [{id, bname, bprice}, ...state]
// 仅限新版本中这样操作
// state.unshift({id, bname, bprice})
// return state
case 'brand/edit': // 编辑数据
// // 获取要编辑的数据
// const brand = state.find(item => item.id === action.data.id)
// // 更新数据
// brand['bname'] = action.data.bname
// brand['bprice'] = action.data.bprice
// 获取要编辑的数据的索引
const index = state.findIndex(item => item.id === action.data.id)
state[index].bname = action.data.bname
state[index].bprice = action.data.bprice
// return [...state]
return state
case 'brand/del': // 删除数据
return state.filter(item => item.id !== action.data.id);
default: // 默认获取数据
return state
}
}
// 4、创建store对象
const store = createStore(brandReducer)
// 5、导出store
export default store
注意事项:
redux
官方不推荐在reducer
数据处理函数中,直接操作state
内部数据,在旧版本中可能会出现数据推送无法更新的情况;新的版本中大部分的数据更新问题已经得到了改善;所以上述开发规范和规则大家了解即可!
项目开发过程中,根据不同的功能,将代码拆分到不同的模块中进行管理:结构化拆分
如:redux
管理状态数据的方式,如图所示:
① 创建品牌数据管理模块文件夹:
src/store/brand/
② 结构化拆分:src/store/brand/actionType.js
/** 操作类型 */
// 按需导出模块
export const brandAddType = "brand/add"
export const brandEditType = "brand/edit"
export const brandDelType = "brand/del"
③ 结构化拆分:src/store/brand/actionCreator.js
/** 数据操作包装类型 */
import {
brandAddType,
brandEditType,
brandDelType
} from './actionType'
export const brandAddAction = data => ({type: brandAddType, data})
export const brandEditAction = data => ({type: brandEditType, data})
export const brandDelAction = data => ({type: brandDelType, data})
④ 结构化拆分:src/store/brand/brandReducer.js
import {
brandAddType,
brandEditType,
brandDelType
} from './actionType'
// 构建初始数据
const initData = [
{id: 2, bname: '华为', bprice: 8999},
{id: 1, bname: '小米', bprice: 5999}
]
// 构建reducer合并函数
export default function brandReducer(state=initData, action) {
switch(action.type) {
case brandAddType:
let {bname, bprice} = action.data
let id = state.length > 0 ? state[0].id+1:1
return [{id, bname, bprice}, ...state]
case brandEditType: // 编辑数据
// 获取要编辑的数据的索引
const index = state.findIndex(item => item.id === action.data.id)
state[index].bname = action.data.bname
state[index].bprice = action.data.bprice
// return [...state]
return state
case brandDelType: // 删除数据
return state.filter(item => item.id !== action.data.id);
default: // 默认获取数据
return state
}
}
⑤ store/index.js
中管理数据
// 引入依赖的模块
import { legacy_createStore as createStore} from 'redux'
// 引入需要处理的数据
import brandReducer from './brand/brandReducer'
// 创建store对象
const store = createStore(brandReducer)
// 导出store
export default store
⑥ react
组件中,调用操作数据:
addBrand() {
if (this.state.brandInfo.trim().length <= 0) {
alert("输入的数据不能为空")
return
}
// 拆分解构数据
let [bname, bprice] = this.state.brandInfo.split(',')
if (this.state.brandId) {
// 编辑
// dispatch()发起了一个更新actionCreator操作
// 原来:store.dispatch({type: 'brand/edit',
// data:{id: this.state.brandId, bname, bprice}})
// 现在:封装了actionCreator.js模块,组件中就不需要手工包装creator对象
store.dispatch(brandEditAction({id: this.state.brandId, bname, bprice}))
} else {
// 新增
// dispatch()函数执行,会自动调用reducer函数完成数据处理
// 原来:store.dispatch({ type: 'brand/add', data: { bname, bprice } })
// 现在:封装了actionCreator.js模块,组件中就不需要手工包装creator对象
store.dispatch(brandAddAction({ bname, bprice }))
}
}
一个项目中,不止包含一个需要处理的数据,需要状态管理的数据非常多,如何通过store
对大量数据进行状态管理:模块化拆分
Vue
结合Vuex
如何实现模块化拆分?
- 将需要管理的数据,封装到一个独立的模块中
- 主模块中通过
modules
选项进行管理
可以通过redux
提供的combineReducers()
函数,完成多个模块化数据的绑定:
// 引入依赖的模块
import {
legacy_createStore as createStore,
combineReducers
} from 'redux'
// 引入需要处理的数据
import brandReducer from './brand/brandReducer'
import goodsReducer from './goods/goodsReducer'
// 创建store对象
// const store = createStore(brandReducer)
const store = createStore(combineReducers({
brand: brandReducer,
goods: goodsReducer,
// ...
}))
// 导出store
export default store
注意事项:redux
完成数据模块化管理之后,每个数据都属于自己的数据模块,此时react
组件中使用状态数据,只对读取数据产生影响(需要读取指定模块的数据);对数据增删改查操作不会影响,可以直接使用
react
组件中,通过redux
管理状态数据之后,组件内部出现了大量和redux
相关的语法,造成了react
组件在使用时严重依赖redux
模块的高度耦合的情况;
redux
官方提供了一个数据辅助模块:react-redux
,用于辅助react
组件操作redux store
数据
进入项目目录,安装依赖模块
$ npm install react-redux -S
编辑项目入口模块:src/App.jsx
,将redux
数据托管到根组件上
import './App.css';
import Brand from './views/Brand'
// 导入托管组件
import { Provider } from 'react-redux'
// 导入公共数据
import store from './store';
function App() {
return (
{/* 数据托管,将数据通过Provider组件托管到项目中 */}
入口模块
);
}
export default App;
state
数据编辑目标组件src/views/Brand/index.jsx
,修改导出方式:
import React, { Component } from 'react'
import store from '../../store'
import {
brandAddAction,
brandEditAction,
brandDelAction
} from '../../store/brand/actionCreator'
// 1. >>>>>>>>>>>>>>> 引入react-redux中导出组件的高阶组件
import { connect } from 'react-redux'
import './Brand.css'
class Brand extends Component {....}
// 2. >>>>>>>>>>>>>>> 通过react-redux高阶组件connect,导出当前组件
export default connect()(Brand)
编辑src/views/Brand/index.jsx
,添加state.brand
模块数据的解耦合:
....
// mapState(...)
// 页面组件中使用数据耦合语法:store.getState().brand.map(item => (....))
// 解耦合后的语法:this.props.brandList.map(item => (.....))
const mapStateToProps = state => {
return {
brandList: state.brand
}
}
// 通过react-redux高阶组件connect,导出当前组件
export default connect(
mapStateToProps // 解耦合state数据
)(Brand)
actions
操作编辑src/views/Brand/index.jsx
,添加解耦合操作语法
// mapState(...)
const mapStateToProps = state => {
return {
brandList: state.brand
}
}
// mapActions(...)
const mapActionsToProps = dispatch => {
return {
addBrand: data => dispatch(brandAddAction(data)),
editBrand: data => dispatch(brandEditAction(data)),
delBrand: data => dispatch(brandDelAction(data))
}
}
// 通过react-redux高阶组件connect,导出当前组件
export default connect(
mapStateToProps, // 解耦合state数据
mapActionsToProps // 解耦合actions操作
)(Brand)
项目中的数据,包含数据存储、数据的操作,每个存储的数据包含CRUD
各种操作方式,解耦合语法中可以通过mapStateToProps
将数据添加到当前组件的属性上使用,但是每个数据对应的actions
操作太多不方便项目的开发和维护,现有的代码对于Actions
操作函数的映射不太友好:
...
// mapState(...)
const mapStateToProps = state => {
return {
brandList: state.brand
}
}
// 后期可能一个页面中使用多组数据,导致actions映射函数过多
// mapActions(...)
const mapActionsToProps = dispatch => {
return {
addBrand: data => dispatch(brandAddAction(data)),
editBrand: data => dispatch(brandEditAction(data)),
delBrand: data => dispatch(brandDelAction(data))
}
}
...
redux
提供了actions
映射函数的自动绑定操作
...
// 导入指定模块中的所有的actionCreators
import * as actions from "../../store/brand/actionCreators"
// 导入辅助函数,将actionCreators绑定到当前组件
import { bindActionCreators } from "redux"
...
// 原始的绑定方式,需要将每个actionCreator函数 显式的进行绑定:导致当前组件中函数绑定过多
// const mapActionsToProps = dispatch => {
// return {
// addBrand: data => dispatch(brandAddAction(data)),
// editBrand: data => dispatch(brandEditAction(data)),
// delBrand: data => dispatch(brandDelAction(data))
// }
// }
// 使用辅助函数的方式,通过一行代码将所有的actionCreators绑定到当前组件
// 完成数据操作函数的绑定,绑定到当前组件的函数名称,和actionCreators函数名称一致时
const mapActionsToProps = dispatch => bindActionCreators(actions, dispatch)
// 通过react-redux高阶组件connect,导出当前组件
export default connect(
mapStateToProps, // 解耦合state数据
mapActionsToProps // 解耦合actions操作
)(Brand)
核心知识点:闭包函数
① 什么是闭包函数
> 概念
闭包函数(closure),描述了一种在函数内部声明函数的函数声明语法!
> 特点
闭包函数可以让外部函数常驻内存,扩展了外部函数中局部变量的作用域链
> 注意
闭包函数很容易让外部函数中的局部变量数据常驻内存,使用时注意数据的使用范围并及时清除,否则很容易造成内存泄漏导致程序崩溃的情况出现
② 代码案例理解闭包函数
// 开发一个出租车计价器;
// 注意:计价器~计算的数据,不能存储在全局变量中,避免全局污染问题
// 不能使用面向对象开发,不能存储在对象中
// 要求:通过函数式开发,开发一个计价程序
// 实现多段计价,堵车-5块、正常行驶 3块/公里
// 有一个出租车--一段计价里程:行驶2公里、堵车1次、行驶3公里、堵车1次、行驶2公里到达目的地
function txt() {
// 计价总额
let money = 0
// 闭包函数:声明一个计价器
function calculation(sts, dsc=0) {
if(sts === '正常行驶') {
money += dsc * 3;
}else if(sts === '堵车') {
money += 5
}
return money
}
// 返回闭包函数的声明
return calculation
}
// 通过执行函数,获取一个计价器
let cal = txt()
cal('正常行驶', 2) // 6
cal('堵车') // 5
cal('正常行驶', 3) // 9
cal('堵车') // 5
const money = cal('正常行驶', 2) // 6
console.log("计算总价:", money) // 31
③ 项目中的应用场景
1、自定义组件封装,闭包函数+自执行函数
2、防抖
3、节流
4、页面动画
5、高频率事件
6、统计数据
...
面试题:请描述数组操作中的map
和forEach
有什么区别?
习惯性操作:通过代码去验证、通过百度去搜索,但是往往都得不到最权威的解答!
所有的技术内容,都应该有自己官方或者权威的文档可以查询
建议:可以搜索mdn
文档,是从官方英文文档直接翻译的比较权威的描述文档
回答思路:先说共同点(概念描述)、再说差异,最终总结在项目中的应用方式
回答方式(了解):
map()
函数和forEach()
函数都可以对目标数组执行遍历的操作;map()
函数可以遍历数据,并针对每个遍历的数据执行目标函数并返回结果,最后将返回的结果组成一个新的数组;forEach()
函数只是遍历目标数组,将每个遍历的数据执行目标函数完成数据处理就结束!我的项目中一般需要按照指定的规则转换数组数据时使用map()
函数,如果只是遍历数据并处理数据时使用forEach()
循环