Redux 实际应用

1,014 阅读6分钟

1. redux 是什么?

  • redux 是一个独立专门用于作状态管理的JS库(不是react插件)
  • 它可以用于react、angular、vue等项目中,但基本于react配合使用
  • 作用:集中式管理react应用中多个组件共享的状态

2. 使用场景

  • 总体原则:能不使用就不用,如果不用比较吃力才考虑使用
  • 某个组件的状态需要共享
  • 某个状态需要在任意位置都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

3. redux 的核心API

3.1. createrStore()

  • 作用:创建包含指定reducer的store对象
import {createrSotre} from 'redux'
import counter from './reducers/counter'
const store = createrStore(counter)

3.2. store 对象

  • 作用:redux 库最核心的管理对象
  • 它内部维护着:
    1. state 存放数据
    1. reducer是根据旧状态产生一个新状态的函数
  • 核心方法
    1. getState() 获取状态显示
    1. dispatch(action) 分发事件
    1. subscribe(listener) 订阅监听,更新/重新渲染组件
store.getState()
store.dispatch({type: 'INCREMENT', number})
store.subscribe(render)

3.3. applyMiddleware()

  • 作用:应用上基于redux的中间件
import {createStore, applyMIddleware} from 'redux'
import thunk from 'redux-thunk' // redux异步中间件
const store = createStore(
    counter,
    applyMiddleware(thunk) // 应用上异步中间件
)

4. redux 的三个核心概念

4.1. action

唯一改变state的方法就是触发action,action是一个用于描述以发生事件的普通对象 记住,action只是描述了有事情发生了这一事实,并没有描述如何更新state

  • 标识要执行行为的对象
  • 包含2个方面的属性:
    1. type:标识属性,字符串,唯一,必要属性
    1. data:数据属性,任意值类型,可选属性
const action = {type: 'INCREMENT', data: 2}
  • action creator(创建action工厂函数,只是简单的返回一个action)
const increment = (number) => ({type: 'INCREMENT', data: number})

4.2. reducer

为了描述action如何改变state,你需要编写reducer Reducer 只是一些纯函数,它接收先前的state和action并返回新的state

  • 根据旧的state和action,产生新的state的纯函数
export default function counter(state=0, action) { 
// 根据实际情况state要有同等数据类型的默认值
    switch(action.type) {
        case 'INCREMENT':
            return state + action.data
        case 'DECREMENT':
            return state - action.data
        default: // 一般都要写default
            return state
    }        
}

注:返回一个新的state,必要修改旧的state

4.3. store

整个应用的state被存储在一颗object tree中,并且这个object tree只存在于唯一一个store中

  • 作用:将state、action、reducer联系在一起的对象
import {createStore} from 'redux'
import reducer from './reducers
const store = createStore(reducer)
  • 此对象的功能:
getState() // 获取state
dispatch(action) // 分发action,触发reducer调用,生成新的state
subscribe(listener) // 注册监听,当产生新的state时自动调用

5. 使用redux编写应用

5.1. 目录结构

redux
  --action-types.js 包含所有action type的常量字符串
  --actions.js 包含所有的 action creator
  --reducers.js 包含n个reducer函数的模块
  --store.js 生成store对象
index.js 程序入口
  • action-types.js
// 包含所有的action type的常量字符串
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT
  • actions.js
// 包含所有的 action creator
import {INCREMENT,DECREMENT} from './action-types'
// 增加
export const increment = (number) => ({type: INCREMENT, data: number})
// 减少
export const decrement = (number) => ({type: DECREMENT, data: number})
  • reducer.js
// 包含n个reducer函数的模块
import{INCREMENT, DECREMENT} from './action-types.js'
export function counter (state=0, action) {
    switch(action.type) {
        case INCREMENT:
            return state + action.data
        case DECREMENT:
            return state -action.data
        default:
            return state
    }
}
  • store.js
import {createStore} from 'redux'
import {counter} from './reducers'
// 生成一个store对象
const store = createStore(counter)
export default store
  • index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './redux/store'
function render() {
    ReactDOM.render(<App store={store) />,
    document.getElementById('root'))
}
// 初始化渲染
render()
// 订阅监听,state状态改变,就会自动进行重绘
store.subscribe(render)
  • 在组件中使用
import React from 'react'
import * as actions from './redux/actions'
class App extends React.Component {
    constructor(props) {
            super(props)
    }
handleAdd = () => {
    const currentNumber = this.select.value*1
    this.props.store.dispatch(actions.increment(currentNumber))
}
handleOdd = () => {
    const count = this.props.store.getState()
    if(count%2 === 1) {
        const currentNumber = this.select.value*1
        this.props.store.dispatch(actions.increment(currentNumber))
    }
}
handleAsync = () => {
      const currentNumber = this.select.value * 1
      setTimeout(() => {
        this.props.store.dispatch(actions.increment(currentNumber))
      }, 1000)
}
    render () {
      // 获取 state
      const count = this.props.store.getState()

      return (<div>
        <p>click {count} times</p>
        <select ref={select => this.select = select}>
          <option value="0">0</option>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.handleAdd}>+</button>&nbsp;
        <button onClick={this.handleDecrement}>-</button>&nbsp;
        <button onClick={this.handleIfOdd}>increment if odd</button>&nbsp;
        <button onClick={this.handleAsync}>increment async</button>
      </div>)
    }
  }
  export default App

5.2 问题

  • redux于react组件代码耦合度太高
  • 编码不够简洁

6. react-redux

一个react插件库

专门用来简化react应用中使用redux

6.1. react-redux 将所有组件分为两大类

  • UI组件
    1. 只负责UI的呈现,不带有任何业务逻辑
    1. 通过props接收数据
    1. 不使用任何redux的API
    1. 一般保存在components文件夹下
  • 容器组件
    1. 负责管理数据和业务逻辑,不负责UI
    1. 使用redux的API
    1. 一般保存在containers文件夹下

6.2. 相关 API

  • Provider
// 所有组件都可以得到state数据
<Provider store={store}>
    <App />
</Provider>
  • connect()
// 将UI组件生成容器组件,将组件和redux相关联
import {connect} from 'react-redux'
import Counter from '../components/counter' // UI
connect(
    mapStateToProps, // 将状态映射为属性
    mapDispatchToProps // 将方法映射为属性
)(Counter)

7. 使用react-redux

  • 下载依赖包
npm i --save react-redux
  • 目录结构
// 增加
components
  --counter.js // UI
container
  --app.js // 容器组件
  • counter.js
// UI
import React from 'react'
import PropTypes from 'prop-types'

export default class Counter extends React.Component {
  // constructor(props) {
  //   super(props)
  //   this.state = {}
  // }
  static propTypes = {
    count: PropTypes.number.isRequired,
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired
  }
  handleAdd = () => {
    const currentNumber = this.select.value * 1

    this.props.increment(currentNumber)
  }
  handleDecrement = () => {
    const currentNumber = this.select.value * 1
    
    this.props.decrement(currentNumber)
  }
  handleIfOdd = () => {
    // 获取
    const { count } = this.props
    if (count % 2 === 1) {
      const currentNumber = this.select.value * 1
      // 改变
      this.props.increment(currentNumber)
    }
  }
  handleAsync = () => {
    const currentNumber = this.select.value * 1
    setTimeout(() => {
      this.props.increment(currentNumber)
    }, 1000)
  }
  render () {
 this.props.store.getState()
    const { count } = this.props
    return (<div>
      <p>click {count} times</p>
      <select ref={select => this.select = select}>
        <option value="0">0</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>&nbsp;
      <button onClick={this.handleAdd}>+</button>&nbsp;
      <button onClick={this.handleDecrement}>-</button>&nbsp;
      <button onClick={this.handleIfOdd}>increment if odd</button>&nbsp;
      <button onClick={this.handleAsync}>increment async</button>
    </div>)
  }
}
  • app.js
import React from 'react'
import {connect} from 'react-redux'
import {increment, decrement} from '../redux/actions'
import Counter from '../components/counter'
export default connect(
    state => ({count: state}),
    {increment, decrement}
)(Counter)
  • index.js
import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'
import App from './containers/app'
import store from './redux/store'
ReactDOM.render(<Provider store={store}>
    <App />
</Provider>, document.getElementById('root'))
  • 问题
  1. redux 默认泵惊醒异步处理
  2. 应用中需要在redux中执行异步任务

8. redux 异步编程

8.1. 下载异步插件

npm i --save redux-thunk

8.2. 在store.js中使用

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

const store = createStore(
    counter,
    applyMiddleware(thunk)
)
export default store

8.3. 在 action.js中使用异步

/***
 * 包含所有的 action creator
 * 同步的 action 返回一个对象
 * 异步的 action 返回一个函数
 * 默认只能返回一个对象,加上异步中间件 applyMiddleware(thunk) 才能返回一个函数
 */
import { INCREMENT, DECREMENT } from './action-types'

// 增加
export const increment = (number) => ({ type: INCREMENT, data: number })
// 减少
export const decrement = (number) => ({ type: DECREMENT, data: number })

// 异步action
export const incrementAsync = (number) => {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment(number))
    }, 1000)
  }
}

8.4. counter.js

  handleAsync = () => {
    const currentNumber = this.select.value * 1

    // 异步中间件 applyMiddleware(thunk)
    this.props.incrementAsync(currentNumber)
  }
  #### 8.5. app.js
  ```javascript
import React from 'react'
import { connect } from 'react-redux'

// incrementAsync 异步事件
import { increment, decrement, incrementAsync } from '../redux/actions'
import Counter from '../components/counter' // UI

export default connect(
  state => ({ count: state }),
  { increment, decrement, incrementAsync }
)(Counter)

9. redux 调试工具

9.1. chrome浏览器插件

  • redux-dectools215_1.crx

9.2. 下载依赖包

  • npm i --save-dev redux-devtools-extension

9.3. 在store.js中使用

// 引入一个用来创建store对象的一个函数
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'

import { counter } from './reducers'

// 生成一个store对象
// 内部会第一次调用reducer函数得到初始state
const store = createStore(
    counter,
    composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)
export default store

特别提醒

  • redux 对于初学者有些难度,需要多看几遍
  • 有没看懂的地方欢迎讨论