React06---redux学习

React 06. redux状态管理模式

一、课程大纲

1、redux概述

2、基本语法

3、结构化拆分

4、模块化拆分

5、解耦合

6、总结

二、课程内容

1、什么是redux

Redux JavaScript状态容器,提供可预测化的状态管理,可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验

Redux 除了和 React 一起用外,还支持其它界面库。 它体小精悍(只有2kB,包括依赖)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传React06---redux学习_第1张图片

小总结:什么是redux

redux它是一个独立的javascript状态管理模式,可以应用于客户端应用、服务端应用或者原生应用等不同环境,易于测试,提高了数据管理的开发效率!

2、工作原理

redux是如何和react结合起来,完成数据的状态管理的?

  • react组件,需要数据时,可以通过redux store固定函数store.getState()获取数据
  • react组件,需要操作数据时,可以创建一个actionType,如增加品牌数据可以是"brand/add"
  • 需要包装一个操作数据对象,可以创建一个actionCreator,如{type: 'brand/add', data:数据}
  • 将包装的数据对象,交给reducer合并函数,执行函数并自动同步数据,将数据同步到state,页面组件中重新获取到更新后的数据

React06---redux学习_第2张图片

3、基本语法

(1) 初始化项目

创建项目

$ 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")
)

(2) 创建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组件需要管理数据就在当前组件中直接导入使用即可!

(3) 组件中读取数据

编辑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}
) } }

(5)组件中添加数据

编辑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

4、关于面试

面试的问题,主要集中在JS和框架

1、原生jsjsonp跨域的原理?

2、原生jsMap类型和Set类型的区别?

3、原生js中数组的map()函数和forEach()函数的区别?

4、是如何理解原生js闭包函数?

5、watchcomputed区别?

6、 Vue数据绑定,普通变量绑定和数组绑定区别?数组是如何绑定的?

7、element中表格展示数据,页面跳转后回到当前页面,如何保持上一次选择状态?

8、如何理解原生js中原型链?

9、Vue生命周期都有哪些?父子组件嵌套生命周期执行顺序?

10、elementui和其他的一些框架vant、layui、lve的区别?

11、介绍一下原生js中的BOM

12、介绍一下原生js中获取节点的函数?

13、介绍一下ES6?

14、手写一下原生JS中的ajax操作步骤?

15、说明一下普通函数和箭头函数的区别?

5、编辑/删除数据

编写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内部数据,在旧版本中可能会出现数据推送无法更新的情况;新的版本中大部分的数据更新问题已经得到了改善;所以上述开发规范和规则大家了解即可!

6、结构化 拆分

(1) 概述

项目开发过程中,根据不同的功能,将代码拆分到不同的模块中进行管理:结构化拆分

如:redux管理状态数据的方式,如图所示:

  • 按照代码功能,将代码拆分到不同的文件中进行管理的方式,称为结构化拆分
  • 按照数据单元,将不同的数据的处理代码,封装到不同的文件夹中,称为模块化拆分

React06---redux学习_第3张图片

(2)品牌数据结构化拆分

① 创建品牌数据管理模块文件夹:

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 }))
  }
}

7、模块化拆分

一个项目中,不止包含一个需要处理的数据,需要状态管理的数据非常多,如何通过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组件中使用状态数据,只对读取数据产生影响(需要读取指定模块的数据);对数据增删改查操作不会影响,可以直接使用

8、解耦合

react组件中,通过redux管理状态数据之后,组件内部出现了大量和redux相关的语法,造成了react组件在使用时严重依赖redux模块的高度耦合的情况;

redux官方提供了一个数据辅助模块:react-redux,用于辅助react组件操作redux store数据

(1) 安装

进入项目目录,安装依赖模块

$ npm install react-redux -S

(2) 数据托管

编辑项目入口模块: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;

(3) 解耦合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)

(4) 解耦合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)

9、解耦合辅助函数

项目中的数据,包含数据存储、数据的操作,每个存储的数据包含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)

10、面试题解析

(1) 疯狂出租车

核心知识点:闭包函数

① 什么是闭包函数

> 概念
闭包函数(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、统计数据
...

(2) 版权所有,翻版必究

面试题:请描述数组操作中的mapforEach有什么区别?

习惯性操作:通过代码去验证、通过百度去搜索,但是往往都得不到最权威的解答!

所有的技术内容,都应该有自己官方或者权威的文档可以查询

建议:可以搜索mdn文档,是从官方英文文档直接翻译的比较权威的描述文档

回答思路:先说共同点(概念描述)、再说差异,最终总结在项目中的应用方式

回答方式(了解):map()函数和forEach()函数都可以对目标数组执行遍历的操作;map()函数可以遍历数据,并针对每个遍历的数据执行目标函数并返回结果,最后将返回的结果组成一个新的数组;forEach()函数只是遍历目标数组,将每个遍历的数据执行目标函数完成数据处理就结束!我的项目中一般需要按照指定的规则转换数组数据时使用map()函数,如果只是遍历数据并处理数据时使用forEach()循环

你可能感兴趣的:(学习,react.js,javascript)