Redux技巧指南

10 篇文章 0 订阅
3 篇文章 0 订阅

Redux基础

Redux在任何地方都可用,不只是运用于React
redux用于state的管理,管理的是在整个网站各个地方都通用的state,但是不去管理那些只作用于组件自身的state
npm install redux -save

作用:将想要全局共享的公共数据(类似static数据)通过state进行保存,只能通过action进行修改,修改后影响全局


要点

  • 项目中所有的state都以一个对象树的形式存储在一个单一的store中
  • 只有触发action才能更改state
  • 通过编写reducers来实现action更改state的逻辑
import { createStore } from 'redux'

// 创建一个store,createStore必须接受一个reducer,reducer为一个方法
let store = createStore(counter)


// 声明一个reducer,接收旧的state和action,根据action来制定修改state的逻辑代码并返回新的state
const state = 0
function counter(state, action) {
    switch(action.type) {
        case '+':
            return state + 1
        case  '-':
            return state - 1
        default:
            return state   // 因为不是任何时候state都需要修改的,因此需要不改变state的出口
    }
}

// action是一个普通的JS对象,普遍约定action内必须使用一个string的type字段来表示将要执行的动作
const add = {type: '-'}
const dec = {type: '+'}

// 改变内部state的唯一方法树dispatch一个action
store.dispatch(add)		// -1
store.dispatch(dec)		// 0


// 创建监听,一般用于更改state时进行渲染更新
store.subscribe()

store

store = createStore(reducer)

store.getState()	 			// 获取当前state内容
store.dispatch(action)			// 方法更新state
store.subscribe(listener)		// 注册监听器,会返回一个可以解绑监听器的函数,执行该函数则会解绑
  • store.getState( )

    获取所有传入reducer中的state的一个整合对象,该对象在每一次dispatch之后会更新

function A(state=0, action:any) {
    switch(action.type) {
        case("ADD"):
            state += 1
            return state
        default:
            return state
    }
} 

function B(state={state:0}) {
    return state
} 

let reducer = combineReducers({A, B})	// 最终state中显示的是什么属性名跟这里传进来的名字对应
let store = createStore(reducer)

store.getState()				// { A:0, B:{state:0} }
store.dispatch({type:'ADD'})
store.getState()				// { A:1, B:{state:0} }
  • store.dispatch(action)

    触发state变化的唯一途径,会使用当前getState()和传入的action以同步方式调用store的reduce函数,返回值会被作为下一个state,成为getState()的新返回值,同时变化监听函数(change listener)被触发,当dispatch 时,会触发所有的 reducer 函数,会根据指定的 action 条件触发对应的 reducer

    dispatch()方法的的参数必须是一个对象,如果是一个方法则会报错,因此action是一个创建函数,要store.dispatch(action())

//从发送action到redux内的state更新这一过程是同步的(即dispatch是同步操作),如果只是简单观察显现可能呈现出异步的效果,这是因为React的setState异步导致的

// 能确保执行完dispatch(A)只会才开始执行dispatch(B),不需要额外加上await
demo = anync () => {
    dispatch(A)
    await request.post(...)	 // 假设这里执行了一些异步操作
    dispatch(B)
}
  • store.subscribe(listener)

    每当执行store.dispatch(action)时便会自动执行此方法,需要操作当前state时可用store.getState()获取当前state


action

action在声明reducer时并没有将具体的的action传入,真正将具体action传入reducer是在调用dispatch(action)的时候

使用action的两种方法:

  • 直接使用一个对象
const action = {
	type:''
}
  • action创建函数,使用一个函数返回一个对象(约定传入参数放进payload属性里)
const action = (prams) => {
    return {
        type:''
        payload: {
        	prams	
    	}
    }
}

reducers

通过触发store的dispatch方法,会触发reducer方法,reducer会拿到dispatch传入的action,再对action内的type属性值进行判断并采用对应方式更改state

这里触发的reducer实际上是 `store = createStore(reducer)` 中的reducer【使用combineReducers方法整合所有reducer】
而该reducer是所有reducer的集合
因此实际上当执行 `store.dispatch(atcion)` 时,会将action传入该集合,相当于触发了所有的reducer,然后执行满足条件的那一块语句

reducer函数拥有两个参数:初始state和传入的action,当触发 dispatch(action)时,会将该action传递给reducer的第二个参数

const reducer = (initState, action) {
    ...
}
  • 设计state结构

    redux中所有state被保存在一个单一对象中,不同类型的state需要想办法进行区分

{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
  • 保证reducer纯净

    不可在reducer里执行如下操作:

    • 修改传入参数
    • 执行有副作用的参数,如API请求和路由跳转
    • 调用非纯函数,如Data.now()Math.random(),这些函数每次调用返回的都是不同结果

    只要传入参数相同,返回计算得到的下一个state就一定相同,没有特殊情况,没有副作用,没有API请求,没有变量修改,单纯执行计算

    reducer的最终目的只是接受一个条件并根据条件修改state,其他不是以修改state为目的的操作应该都在外部操作,如在action中将拿到的数据处理完毕后再将最终结果传递给reducer执行

  • Action

    redux首次执行时,state为undefined,可另设置初始state

function todoApp(state = initialState, action) {
  // 这里暂不处理任何 action,仅返回传入的 state
  return state
}

目录管理

方式一:统一管理reducer和action

因为每次 dispatch(action) 都会触发所有的 reducer,任意 action 也可以在任意一个组件的 dispatch 中使用,因此为了能方便看到所有的 action 和 reducer,可以统一写在一个文件里,同理 action 的 type 也可以全部写在一个文件里方便导入

目录结构

src
  |
  |__ actions
  |		|__ index.js
  |		|__ actionTypes.js
  |
  |__ reducers
  |  	|__ index.js
  |
  |__ store.js
  • 管理reducers
    在项目src目录下创建reducers文件夹,用于统一存放reducer文件,创建index.js文件,用于统一导入reducer文件并导出
import reducerA from './reducerA'
import reducerB from './reducerB'
import {combineReducers} from 'redux'	// redux专门用来提供合并reducers的工具
export default combineReducers({
    reducerA,	// 相当于 reducerA: rerducerA
    reducerB
})

/*
    不可以直接导出,这样导出的不是一个方法,无法被识别
    
	export default {
    	reducerA,
    	reducerB
	}

*/

​ 一开始进行reducer的编写时,只需写明state和声明一个简单方法导出即可,后续再加功能

const reducerA_state = {
    // ...
}

export default (state = reducerA_state, action) => {
    switch(action.type) {
    	default:
    	return state
    }
}
  • 管理store

    在项目的src目录下创建store.js文件(注意只能存在一个store)

import {createStore} from 'redux'
import rootReducer from './reducers'

export default createStore(rootReducer)
  • 管理actions

    在项目src目录下创建actions文件夹,用于统一存放action文件,一般新建一个actionType.js文件统一对action的type进行管理并导出,然后另创建具体的action.js文件使用action创建函数的方式导出具体action

    如:实现一个商品计数器的action

// actionType.js
export default {
    CART_AMOUNT_INCREMENT: 'CART_AMOUNT_INCREMENT',
    CART_AMOUNT_DECREMENT: 'CART_AMOUNT_DECREMENT' 
}
//  /src/actions/cart.js
import actionType from './actionType'
export const increment = (id) =>{
    return {
        type: actionType.CART_AMOUNT_INCREMENT,
        payload: {
            id
        }
    }
}

export const decrement = (id) => {
    return{
        type: actionType.CART_AMOUNT_DECREMENT,
        payload: {
            id
        }
    }
}

​ 在reducer里导入的是actionType.js,在使用store.dispatch( )方法的组件中导入cart.js

//  /src/reducers/cart.js
import actionType from '../actions/actionType'

const initState = [{
    id: 1,
    title: 'Apple',
    price: 8888.66,
    amount: 10
},{
    id: 2,
    title: 'Orange',
    price: 4444.66,
    amount: 12
}]

export default (state = initState, action) => {
    switch(action.type) {
        case actionType.CART_AMOUNT_INCREMENT:
            return state.map(item => {
                if(item.id === action.payload.id) {
                    item.amount += 1
                }
                return item
            })
        default:
            return state
    }
}
import store from '../../store'
import {increment, decrement} from '../../actions/cart'
// ...
<button onClick = {
  () => {
      this.store.dispatch(decrement(id))
  }  
}/>
<button onClick = {
  () => {
      this.store.dispatch(increment(id))
  }  
}/>

方式二:按功能管理reducer和action

/src 下建立 store 目录,在该目录下新建一个 index.js 文件,用于创建 redux 和引入中间件

store 目录下根据功能创建各功能目录,在该目录内创建功能涉及到的 action 和 reducer 和 types

这样就能很明确的指定哪些功能有哪些 action 和 会触发哪些 reducer

不按组件划分的原因可能会有很多组件用到了同样的 reducer 和 action 功能,他们很多时候不只是服务于一个组件的,用组件的关系划分可能会存在很多重复

具体该如何分开这些功能则按具体项目要求决定,如一个小型网站只有用户和用户操作的功能
则可以分为两块,如 user 和 doing 文件
user文件:负责用户的登入注册登出等这些操作所需要的reducer和action功能
doing文件:负责用户的留言点赞转发等这些操作所需要的reducer和action功能

目录结构

src
 |	
 |__ store
      |
      |__ user
      |    |__ actions.js
      |    |__ reducer.js
      |    |__ types.js
      |
      |__ index.js

recat-redux

npm install react-redux -s

react-redux是redux对react的官方绑定库,借助react-redux可以很方便的在react中使用redux

react-redux需要配合redux来使用,redux对外暴露了以下方法,除了这些都是react-redux的内容

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

使用步骤:

  1. 通过createStore()方法创建store;

  2. 通过Provider组件将store注入到需要使用store的组件中;

  3. 通过connect()连接UI组件和容器组件,从而更新state和dispatch(action)

运用思想

实际项目中,需要权衡是直接使用Redux还是用React-Redux

  • React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)

  • UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑,如果一个组件既有 UI 又有业务逻辑,则将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件,前者负责与外部的通信,将数据传给后者,由后者渲染出视图

  • React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成,也就是说,用户负责视觉层,状态管理则是全部交给它

UI组件

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 没有状态(即不使用this.state这个变量)
  • 所有数据都由参数(this.props)提供
  • 不使用任何 Redux 的 API

容器组件

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态state
  • 使用 Redux 的 API
UI组件容器组件
作用描述如何展现(骨架、样式)描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源props监听 Redux state
数据修改从 props 调用回调函数向 Redux 派发 actions
调用方式手动通常由 React Redux 生成

组件交流

Provider

使用provider进行快捷组件交流,不需要多层传递数据

使用时需要将其包在要传递组件的最外层,由于<App/>为组件渲染根元素,因此任意子组件内都可直接使用provider传递的数据,无需层层相传,必须要要拥有store属性,值为创建的store

//  /src/index.js

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


render(
    <Provider store={store}>
        <App/>,
    </Provider>,  
    document.querySelector('#root')
)

connect

通过connect( )( )自动生成的容器组件(高阶组件),经过connect操作后会将dispatch方法传入该组件

使用了connect之后是自动订阅state的变化并进行重新渲染的,不需要再去通过store.subscribe(listener)去监听state的变化

import {connect} from 'react-redux'

export default VisibleMyComponent = connect()(myComponent)	// 一般合起来写,作用和下面的一样

/* 
	const VisibleMyComponent = connect()(myComponent)
 	export default VisibleMyComponent


	使用装饰器的写法:
	@connect()	// connect()()有两层括号,使用装饰器后会少一个
	class myComponent {...}
	export default myComponent
*/

使用connect后会在原来传进组件props的基础上增加内容

class componentA {}		// props为传递进来的内容

@connect()
class componentA {}		// props为传递进来的内容和dispatch方法

connect( )接受两个参数:mapStateToPropsmapDispatchToProps ,它们定义了 UI 组件的业务逻辑,前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action

它们主要是为了让redux使用更加方便而服务的,即使不设置也不影响其功能

const VisibleMyComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(myComponent)
  • mapStateToProps()

    它是一个函数,返回一个对象,对象内的属性值(一般是state中的属性)会被加到组件的props中,为了更方便的获取到state值

    当在connect绑定该函数时,对应的函数会自动传进来一个值,这个state为 store.getState() 的值

    在组件执行render方法前被执行,因此每次render后的组件状态都与store同步

// 若原组件的props内容为{a:1}
// 使用connect高阶组件化并设置mapStateToProps的组件props为{a:1, addPrpps:xxx, dispatch: function}
const mapStateProps = (state, ownProps) => {	// ownProps为传进该组件的的props
    return {
        addProps:state.myState
    }
}
  • mapDispatchToProps()

它可以是一个函数,也可以是一个对象,相当于封装了一个传入固定action的dispatch方法并添加到组件的props,为了更方便地使用dispatch方法

connect() 传入该形参,则不会再将 dispatch 方法传入props,取而代之的是封装映射的方法

mapDispatchToProps()在组件constructor()中被执行,因而只执行一次

为一个函数时:当在connect绑定该函数会传入dispatch方法,然后手动封装一个触发固定action的dispatch函数,可以写死传入形参也可以不写入具体形参,具体使用时再传入

const increment = (id) =>{
    return {
        type: actionType.CART_AMOUNT_INCREMENT,
        payload: {
            id
        }
    }
}
const decrement = (id) => {
    return{
        type: actionType.CART_AMOUNT_DECREMENT,
        payload: {
            id
        }
    }
}
// 第一个参数用于接受store.dispatch()方法,第二个参数用于接受组件自身的props
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
      add: (id) => { dispatch(increment(id)) },		
      dec: (id) => { dispatch(decrement(id)) }
    }
}

// 封装后:
// 使用 this.props.dispatch(increment(id)) 等同于使用 this.props.add(id)
// 使用 this.props.dispatch(decrement(id)) 等同于使用 this.props.dec(id)

​ 为一个对象时,键值内容应为action对象或action创建函数,会自动dispatch该对象中的所有属性(为创建函数时则是执行后的action结果)

const increment = (id) => {   
    return {
        type: 'CART_AMOUNT_INCREMENT',       
        payload: {
            id
        }
    }
}
const decrement = (id) => { 
    return {
        type: 'CART_AMOUNT_DECREMENT',
        payload: {
            id
        }
    }
} 

const mapDispatchToProps = {    
	add: increment,
    add: decrement,
}
// this.props.add(id)

一般是在action先写好了mapStateToProps和mapDispatchToProps再导出到组件并传到connect里

// 为了展示方便全写在一个文件里,实际在项目中需要分类各个文件
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'


// 定义counter组件
class Counter extends Component {
  render() {
    const { value, onIncreaseClick } = this.props	
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>自增按钮</button>
      </div>
    )
  }
}


// Action  
const increaseAction = { type: 'increase' }


// Reducer   基于原有state根据action得到新的state
function counter(state = { count: 0 }, action) {
  switch (action.type) {
    case 'increase':
      return { count: state.count + 1 }
    default:
      return state
  }
}


// 根据reducer函数通过createStore()创建store
const store = createStore(counter)


//  将state映射到Counter组件的props
function mapStateToProps(state) {
  return {
    value: state.count
  }
}


//  将action映射到Counter组件的props
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction)
  }
}


//  传入上面两个函数参数,将Counter组件变为App组件
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)


ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值