redux

1. redux理解

学习文档

         1)英文文档: Redux - A predictable state container for JavaScript apps. | Redux

          2)中文文档: Redux中文文档

           3)Github: GitHub - reduxjs/redux: Predictable state container for JavaScript apps

redux是什么

        1)redux是一个专门用于做状态管理的JS库(不是react插件库)。

        2)它可以用在react, angular, vue等项目中, 但基本与react配合使用。

        3)作用: 集中式管理react应用中多个组件共享的状态。

                多个组件之间状态共享,A组件将数据放入redux,然后其他组件可以从redux取得数据。

什么情况下需要使用redux

        1)某个组件的状态,需要让其他组件可以随时拿到(共享)。

        2)一个组件需要改变另一个组件的状态(通信)。

        3)总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

 2.redux原理

redux_第1张图片

1)组件根据要做的事,组件自己封装action对象;或者组件根据要做的事委托Action Creators封装一个action对象。  action是js中的一个对象 ,包含type、data两个属性。

2)调用dispatch(action) 方法,分发action对象。

3)Store是全局指挥调度者。dispatch方法将action交给Store,然后Store安排Reducer具体执行。Reducer需要初始化

4)执行结果返回给组件

3.redux核心概念

action

        (1)是一个对象,通过函数返回此对象。

        (2)action对象包含2个属性

                type:标识属性, 值为字符串, 唯一, 必要属性

                data:数据属性, 值类型任意, 可选属性

        (3)例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

reducer

        (1)本质:是一个纯函数

           (2) 作用:用于初始化状态、加工状态。

        (3)加工时,根据旧的state和action, 产生新的state

store

        (1)将state、action、reducer联系在一起的对象

        (2)如何得到此对象?

import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)

        (3)此对象的功能?

                1)getState(): 得到state

                2)dispatch(action): 分发action, 触发reducer调用, 产生新的state

                3)subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

 4.实战项目

项目github地址:GitHub - MeBetterMan/click-redux

项目效果如下

redux_第2张图片

(1)纯react

        不使用redux,使用纯react实现。

        一个组件内,建立select button标签,改变state属性即可实现。 如下为VSCode选择多行快捷键

按住alt,用鼠标左键点击,可以出现多个光标,输入的代码可以在光标处同时增加。

按shift+alt,再使用鼠标拖动,也可以出现竖直的列光标,同时可以选中多列。

选中一段文字,按shift+alt+i,可以在每行末尾出现光标

按住Ctrl + Alt,再按键盘上向上或者向下的键,可以使一列上出现多个光标。

光标放在一个地方,按ctrl+shift+L或者ctrl+f2,可以在页面中出现这个词的不同地方都出现光标。

(2)redux精简版

        使用store、reducer,不使用Action Creators

1)安装redux

        yarn add redux 

2)新建redux文件夹

        src目录下建立components、redux文件夹,分别存放不同用处的js文件。

        每个需要redux的组件,都需要对应一个reducer,所以命名为:组件名xx_reducer,意为专门为组件xx服务的reducer;全局一个store,命名为store.js即可。

redux_第3张图片

3)编写store.js

  • 引入redux中的createStore函数,创建一个store
  • createStore调用时要传入一个为其服务的reducer
  • 记得暴露store对象
import { createStore } from 'redux'
import count_reducer from './count_reducer'

export default createStore(count_reducer)

4)编写count_reducer.js

        reducer本质为一个function,接收preState、action对象两个参数,返回一个newState参数。preState为前次调用的结果。reducer会被store统一管理。

        reducer用于初始化和后续多次调用。第一次调用是store自动触发的,此时preState值为undefined,传递的action是:{type:'@@REDUX/INIT_a.2.b.4},必须返回一个默认值。后续调用store会将前次调用的结果传给preState。

        prevState = initalState 为默认值,如果preState为undefined或者调用时没有传值,那么使用默认值;否则是否传入的值。

const initalState = 0
export default function count_reducer(prevState = initalState, action) {
    const { type, data } = action
    switch (type) {
        case 'add':
            return prevState + data * 1
        case 'sub':
            return prevState - data * 1
        default:
            return initalState
    }

}

5)编写组件

         store.getState()获取store目前存储的状态数据值。

        store.dispatch({ type: 'add', data: value }) 将action对象分发给reducer让其执行,返回新状态存储到store。store存储的状态发生改变,不能够重新渲染页面。必须调用setState方法才可。 this.render()直接这样调用也不会渲染页面。

        store.subscribe(()=>{}) 会在每次store存储的数据发生变化时,执行回调函数。如下代码中,空调setState方法,重新渲染了组件。如果不空调,store状态改变不会显示到页面中。redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写

import React, { Component } from 'react'
import store from '../../redux/store'

export default class Count extends Component {
    add = () => {
        const value = this.selRef.value
        store.dispatch({ type: 'add', data: value })
    }
    componentDidMount() {
        store.subscribe(() => {
            this.setState({})
        })
    }
    render() {
        return (
            

总和是:{store.getState()}

) } }

 6)触发页面渲染

        如果有很多个组件都需要reducer,那么触发重新渲染就需要在每个组件里面都空调setState方法,显然不合适。那么可以在入口index.js文件直接渲染整个全体页面。由于diff算法的存在,也不会全部都渲染,只会渲染有改变的标签,故不会浪费太多性能。

ReactDOM.render(
  ,
  document.getElementById('root')
);

store.subscribe(() => {
  ReactDOM.render(
    ,
    document.getElementById('root')
  );
})

 (3)redux完整版

       完整版就是增加生成action对象的文件。

1)JavaScript小技巧

        对象定义简写方式 

let data = 'hello'
{type:'inc',data:data}

可以省略data属性值,简写为

{type:'inc',data}

        箭头函数简写

箭头函数语法
(arg1,args2...)=>{
    ...
    return ...
}

1.如果1个参数 可以省略小括号
2.如果代码段仅一个返回值 可以圣罗return

data=> return data + 1

但如果返回的是对象,需要小括号包括

data =>({data:data+1}) 

如果下面写法 大括号会被视为代码段的大括号


data =>{data:data+1})

        如果一个常数值被多个文件使用,那么就新建一个constant.js文件模块,然后定义常量值,目的是便于管理和防止程序员写错单词;如果一个文件内多处使用,就在文件最开始定义常量。

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

2)新建action文件

        此文件作用:通过函数生成生成某个容器组件的action对象。如下图的count_acton.js

        每个action针对一个组件,所以命名为 组件名xx_action_creators.js。每个用到action对象的地方直接调用此文件定义的方法返回action即可。

        下图中的constant.js文件用来专门放置常量值。

redux_第4张图片

import { INCREMENT } from "./constant"

export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: '', data })

5.异步action

action可以是一个JS对象,也可以是一个function。是对象时就是同步action,是function时就是异步action。

为什么用异步action?异步的动作不想交给组件执行,想交给action

何时需要异步action?想要对状态进行操作,但是具体的数据靠异步任务返回。

备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

(1) 安装 redux-thunk

         异步action只有安装了redux-thunk中间件,store才可以处理异步action方法。

 yarn add redux-thunk

(2)在store.js中添加中间件

        createStore()添加第二个参数,让store可以处理action为function的参数。

import { createStore, applyMiddleware } from 'redux'
import count_reducer from './count_reducer'
import thunk from 'redux-thunk'

export default createStore(count_reducer, applyMiddleware(thunk))

 (3)编写异步action方法

        1)action方法返回函数

        2)函数内一般会有一个异步任务。

        3)然后异步任务完成后,调用 同步action的方法 比如下面的createIncrementAction就是一个同步的action方法

 
//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{
			dispatch(createIncrementAction(data))
		},time)
	}
}

(4)store调用异步action

            异步action其实就是让store执行异步action返回的function方法。此function等待异步任务执行完成,然后再调用同步action的方法。

store.dispatch(createIncrementAyncAction(value*1,500))

 6.react-redux

 redux_第5张图片

redux为第三方公司开发,react-redux为Facebook开发,专门用来使用redux的一个工具。上图中的redux就是第五小节使用的redux。

需要将组件拆分为容器组件和UI组件,UI组件和redux通过容器组件作为桥梁进行数据交换。

         1).UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。

          2).容器组件:负责和redux通信,将结果交给UI组件。

开发步骤

(1)建立文件夹

        src下新建containers、components文件夹,分别存放容器组件、UI组件。就是以前的一个组件根据功能分为了两个组件,且container组件被react-redux自动设置为ui组件的父组件。文件夹下还是各个组件文件夹。同一个组件容器组件和UI组件名字一样。

redux_第6张图片

(2)容器组件

        容器组件作用是建立redux和UI组件的联系。靠react-redux 的 connect函数创建一个容器组件。 容器组件需要将redux中保存的state和操作state的方法传递给UI组件供其使用,也就是mapStateToProps,mapDispatchToProps这两个方法。                        

        关键方法:connect(mapStateToProps,mapDispatchToProps)(UI组件)

            -mapStateToProps:映射状态,返回值是一个对象。将store.getState()方法返回值以props形式传给UI组件使用。

          -mapDispatchToProps:映射操作状态的方法。返回值是一个对象,对象的value为一个函数,函数中调用dispatch方法分发action对象;也可以直接是一个对象,对象的value为创建action对象的函数。

          -mapStateToProps、mapDispatchToProps就是用户自定义函数,用来获取状态和操作状态,注意其在connet方法中的顺序。名字可任意,这两个名字是react-redux官方推荐。

            备注1: 容器组件的核心就是获取UI组件、获取redux的store。UI组件import导入,store由使用容器组件的父组件通过props传递,store就是上节编写的store。传过来store以后,react-redux会自动将状态和dispatch方法映射给用户自定义函数的形参让用户操作store。

            备注2:mapDispatchToProps,可以是一个函数,也可以是一个对象。是对象时,value为创建action的方法,react-redux自动调用store的dispatch方法,只需要将对象的value设置为action即可;是函数时,需要函数形参接收dispatch函数,然后手写分发action对象。

           备注3:mapStateToProps,mapDispatchToProps函数会通过参数形式自动接收react-redux管理的store传递过来的状态值和分发action对象的dispatch方法。如下代码所示

           备注4:mapStateToProps,mapDispatchToProps函数的返回对象的KV会作为props的KV传递给UI组件。react-redux实现的。

//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
	createIncrementAction,
	createDecrementAction,
	createIncrementAsyncAction
} from '../../redux/count_action'

//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

/* 
	1.mapStateToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapStateToProps用于传递状态
    4.此函数调用者为react-redux 其调动时已经自动将store.getState()方法返回值赋值给了state形参
*/
function mapStateToProps(state){
	return {count:state}
}

/* 
	1.mapDispatchToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapDispatchToProps用于传递操作状态的方法
    4.此函数调用者为react-redux 其调动时已经自动将store.dispatch()方法赋值给了dispatch形参
*/
function mapDispatchToProps(dispatch){
	return {
		jia:number => dispatch(createIncrementAction(number)),
		jian:number => dispatch(createDecrementAction(number)),
		jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
	}
}

//使用connect()()创建并暴露一个Count的容器组件
//connect()()第一个括号定义两个函数a b a为获取状态的函数 b为设置状态的函数 ;第二个括号为UI组件 
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

(3)容器组件的父组件传递store

        App组件时Count容器组件的父组件,通过props传过来store 。store为第五小节定义的store

import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'

export default class App extends Component {
	render() {
		return (
			
{/* 给容器组件传递store */}
) } }

(4)UI组件中使用、操作store中保存的状态

//通过mapDispatchToProps方法返回的对象中的key调用修改store状态的方法
increment = ()=>{
	const {value} = this.selectNumber
	this.props.jia(value*1)
}

//通过mapStateToProps方法返回的对象key获取store存储的状态

当前求和为:{this.props.count}

优化一  mapDispatchToProps为一个对象

        connect(mapStateTpProps,mapDispatchToProps)(UI)

        mapDispatchToProps可以是对象,对象的value为创建action的函数。react-rex会自动调用store的dispatch方法将action分发。简化为如下:

export default connect(
    state => ({ count: state }),
    //通知redux执行加法
    ({ jia: createIncrementAction })
)(CountUI)

优化二 不再使用store.subscribe

        不使用react-redux时,需要store.subscribe方法感知store存储的状态发生改变,然后重新渲染页面。

        使用react-redux后,就不在需要,connect方法自动实现了对store状态的监听,已发生变化,自动渲染UI组件。

store.subscribe(() => {
  ReactDOM.render(
    ,
    document.getElementById('root')
  );
})

优化三 Provider批量进行store传递 

        如果很多个容器组件,都通过如下传递,很冗余




        可以在入口文件index.js中使用react-redux的Provider,将store={store}传递给 App子组件中所有的容器组件。

        步骤 1)引入Provider 2)包裹项目根组件App或者特定容器组件的根组件

import React from 'react';
import ReactDOM from 'react-dom';
import store from './redux/store'
import App from './App';
import { Provider } from 'react-redux';


ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

优化四 容器组件和UI组件定义在一个文件

        如果每个容器都分为容器组件一个文件,UI组件一个文件。容器多了以后,文件会很多,不方便管理。

        一个文件可以定义多个组件。因此,可以将UI组件直接定义在容器组件的文件内,放在containers文件夹下

redux_第7张图片

import { connect } from 'react-redux'
import { createIncrementAction } from '../../redux/count_action'
import React, { Component } from 'react'

//一个文件完成两个工作 1)定义UI组件 2)connect函数返回容器组件
class CountUI extends Component {
    add = () => {
        const { value } = this.selRef
        this.props.addConnect(value)
    }
    render() {
        return (
            

总和是:{this.props.count}

) } } //容器组件 export default connect( state => ({ count: state }), //通知redux执行加法 ({ addConnect: createIncrementAction }) )(CountUI)

优化后react-redux开发步骤

一个组件要和redux“打交道”要经过哪几步?

        0)安装redux 、react-redux 。yarn add redux react-redux

        1)新建redux文件夹,定义好store、reducer、action三个文件

                一个UI组件对应一个reducer、action,因此如果使用store组件多,需要建立reducers、actions分别存储reducer、action

        2)定义好UI组件---不export

        3)使用connect()() 创建并暴露一个容器组件,写法如下:

export default  connect(
    state => ({key:value}), //映射状态 是个箭头函数
    {key:xxxxxAction} //映射操作状态的方法 是个对象
 )(UI组件)

        (4) 入口文件根组件使用Provider组件包裹,完成store的注入。                        

        (5)在UI组件中通过this.props.xxxxxxx读取和操作状态

7.基础版--实现组件间数据共享

源码GitHub地址 click-redux/9_src_最终版 at master · MeBetterMan/click-redux · GitHub

项目效果

        Count组件、Person组件,可以互相访问彼此存在redux中的数据

        redux_第8张图片

1) 建立redux、containers文件夹 

        每一个容器组件都对应一个action和reducer 。所以将redux文件夹分为actions和reducers文件夹。编写person组件的action和reducer,命名就使用对应容器组件的名字。

        容器组件放在containers文件夹下。

redux_第9张图片

reducer  (count.js)

/* 
	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from '../constant'

const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
	// console.log('countReducer@#@#@#');
	//从action对象中获取:type、data
	const {type,data} = action
	//根据type决定如何加工数据
	switch (type) {
		case INCREMENT: //如果是加
			return preState + data
		case DECREMENT: //若果是减
			return preState - data
		default:
			return preState
	}
}

reducer注意点       

        (1) reducer一般不使用push unshift等方法。比如下面代码,就不会返回新值,因为redux发现上一次返回的state和本次返回的state是同一个引用地址值。所以认为数据没有改变,就不返回了。因为push方法修改了传入的形参preState,导致reducer不是纯函数,

preState.push('value') 
return preState

        (2)reducer必须是纯函数 

                一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)。纯函数必须遵守以下一些约束  

                        -- 不得改写函数传入的形参数据

                        -- 不会产生任何副作用,例如网络请求,输入和输出设备

                        -- 不能调用Date.now()或者Math.random()等不纯的方法  

        (3)不同reducer之间的type不能相同,如果不同reducer type相同,当传递此type的action对象时,两个reducer都会执行。(重要)

action (count.js)

/* 
	该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from '../constant'

//同步action,就是指action的值为Object类型的一般对象
export const increment = data => ({type:INCREMENT,data})
export const decrement = data => ({type:DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const incrementAsync = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{
			dispatch(increment(data))
		},time)
	}
}

  2)store里面合并所有reducer

        所有reducer都通过combineReducers合并成一个对象,每个reducer作为value,分配一个任意名称的key。相对应的redux里面的state也是一个对象,key为此处定义的key,从而可以针对指定reducer进行state操作。

        通过此处的key实现了store同时存储不同组件的状态值。一个项目只需一个store,需要多个action和reducer。

        如果reducer很多,都写到store里面进行合并,会和臃肿。可以在reducers文件夹下新建index.js 用来将所有reducer进行合并 ,然后export合并后的allReducers,store引入使用。分文件思想。

store.js

import { createStore, applyMiddleware, combineReducers } from 'redux'
import count_reducer from './reducers/count'
import person_reducer from './reducers/person'
import thunk from 'redux-thunk'

const allReducers = combineReducers(
    {
        count: count_reducer,
        person: person_reducer
    }
)

export default createStore(allReducers, applyMiddleware(thunk))

3)UI组件里面暴露容器组件

        在UI组件代码里,通过react-redux connect方法取得redux和UI组件的关联,通过两个入参函数来获取store中状态和向store存储状态,入参函数返回的对象的key会通过props传给UI。然后UI组件通过props来存入、提取状态。

//使用connect()()创建并暴露一个Count的容器组件
export default connect(
	state => ({
		count:state.count,
		personCount:state.persons.length
	}),
	{increment,decrement,incrementAsync}
)(Count)

4)UI组件调用action方法向redux存入状态

        react-redux自动根据传入的action对象,调用dispatch方法,将action传给对应reducer执行。

//加法
increment = ()=>{
    const {value} = this.selectNumber
    this.props.increment(value*1)
}

 5)state取值

        combineReducers参数值是一个对象,那么对应的state也是一个对象 ,state根据combineReducers传入对象的key分别存储状态值

当前求和为:{this.props.count}

 6)UI组件 count.js

        UI组件中使用store步骤

                ①通过connect方法将UI组件暴露成容器组件 connect方法通过两个入参函数 将获取store状态和通过action向store存入状态暴露给props,UI通过props的key直接使用

                ②UI通过props使用 获取状态和存入状态的方法

import React, { Component } from 'react'
//引入action
import {
	increment,
	decrement,
	incrementAsync
} from '../../redux/actions/count'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

//定义UI组件
class Count extends Component {

	state = {carName:'奔驰c63'}

	//加法
	increment = ()=>{
		const {value} = this.selectNumber
		this.props.increment(value*1)
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		this.props.decrement(value*1)
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		if(this.props.count % 2 !== 0){
			this.props.increment(value*1)
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		this.props.incrementAsync(value*1,500)
	}

	render() {
		//console.log('UI组件接收到的props是',this.props);
		return (
			

我是Count组件,下方组件总人数为:{this.props.renshu}

当前求和为:{this.props.count}

         
) } } //使用connect()()创建并暴露一个Count的容器组件 export default connect( state => ({ count:state.count, personCount:state.persons.length }), {increment,decrement,incrementAsync} )(Count)

8.优化版--实现组件间数据共享

(优化一)reduce文件夹下新增一个index.js 用来合并所有的reducer 

        react文件夹下index.js, 用于combine所有的reducer供store使用  所有项目此代码比较固定

/* 
	该文件用于汇总所有的reducer为一个总的reducer
*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
//引入为Person组件服务的reducer
import persons from './person'

//汇总所有的reducer变为一个总的reducer
export default  combineReducers({
	count,
	persons
})

        store.js 代码固定 可复用。支持reducer、开发者工具、异步调用 。

/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总之后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'

//暴露store 
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))

9.Redux DevTools Google开发者工具

        谷歌商店安装扩展程序 Redux DevTools,用来调试查看store里面存储的状态。

使用步骤 

            (1)yarn add redux-devtools-extension

            (2)store中进行配置

                    import {composeWithDevTools} from 'redux-devtools-extension'

                    const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

        (3)如下是调试工具截图  可以查看action state等,也可以点击箭头处,直接向store存入一个action对象。

redux_第10张图片

你可能感兴趣的:(React,javascript,react.js,前端)