⑧ React Redux 基本用法

查看专栏其它文章:

① React 介绍及JSX简单使用

② React 面向组件编程(state、props、refs)、事件处理

③ React 条件渲染、组件生命周期、表单与事件

④ React 列表与Keys、虚拟DOM相关说明、AJAX

⑤ React 基于react脚手架构建简单项目

⑥ React 项目中的AJAX请求、组件间通信的2种方式(props、消息订阅和发布)

⑦ React 路由解决方案 react-router



本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
学习资料来源于:尚硅谷


Redux 介绍

Redux 内容有点难于总结,因此本文写的比较杂乱。如有需求可直接前往中文文档学习:https://www.redux.org.cn/

redux 是什么?

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

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

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

为什么需要 redux?

从之前的小项目来看,我们其实也实现了 “集中式管理”。我们把相关内容都集中在根组件 App 上,只不过每个子组件中会存放各自的状态(state),修改这些状态的行为也就在各自的组件中。但是对于大型项目来说,会有很多组件 和 路由组件,路由组件甚至也会有子路由组件,也许这些组件有共享的状态,又或者是相关状态,都存放在各自的组件中,寻找和修改时就会比较繁琐又费事费力,还需要组件通信。因此就考虑到:专门设计一个库,将部分状态交给它来管理,再提供一些修改状态的方法,之后哪个组件需要就去调用它即可。

更多内容可参考: 动机

因此根据上述的思路,Redux 的工作流程也可略知一二:对于状态一共就有两种行为,一个是读状态显示,一个是更新状态。所以组件首先是从 Redux 的存储状态区域读取状态,随后遇到更新状态事件,就去进行一系列操作,最后再存入到存储状态区域,给相关组件读取。

那在这其中最关键的两个问题就是:组件如何和Redux交互、Redux内部如何接收事件并实现状态更新。

现在看一下具体的 redux 工作流程:( 相关用法在文章下方就会提及 )
在这里插入图片描述
总结来说整体流程是:React 组件 Components 从 Redux 状态存储 Store 中,获取到状态 state。随后更新状态需要通过 Redux 的 Action Creators 分发 dispatch 事件 action,但是这个事件不能直接去修改 Store 中状态,需要先去 Reducers 中。在 Reducers 中,就会根据事件 action 对之前的状态 previousState 进行操作,随后将新状态 newState 存储进 Store 中。

什么情况下需要使用 redux?

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

  2. 某个组件的状态需要共享

  3. 某个状态需要在任何地方都可以拿到

  4. 一个组件需要改变全局状态

  5. 一个组件需要改变另一个组件的状态


基础用法

在本例中将实现下图功能:次数的值需要通过加减按钮进行变化,加减数量由左侧选择框决定。且当次数为奇数时,点击 increment if odd 按钮才会变化;点击 increment async 将会通过 定时器 setTimeout 异步增加次数。
在这里插入图片描述
为了比对 React 和 Redux 的区别,将会编写这两种代码。


React 版

内容比较简单,项目结构如下:

(如果对基本用法感到困惑,可参考我的 ① ~ ④ 测试语法文章)
在这里插入图片描述
index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/app'

ReactDOM.render(<App/>, document.getElementById('root'))

app.jsx:

import React, {Component} from 'react'

export default class App extends Component {

  state = {
    count: 0
  }

  increment = () => {
    const num = this.refs.numSelect.value*1
    const count = this.state.count + num
    this.setState({count})
  }

  decrement = () => {
    const num = this.refs.numSelect.value*1
    const count = this.state.count - num
    this.setState({count})
  }

  incrementIfOdd = () => {
    let count = this.state.count
    if(count%2==1) {
      const num = this.refs.numSelect.value*1
      count += num
      this.setState({count})
    }
  }

  incrementAsync = () => {
    setTimeout(() => {
      const num = this.refs.numSelect.value*1
      const count = this.state.count + num
      this.setState({count})
    }, 1000)
  }

  render () {
    const {count} = this.state

    return (
      <div>
        <p>
          click {count} times
        </p>
        <select ref="numSelect">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>{' '}
        <button onClick={this.increment}>+</button>{' '}
        <button onClick={this.decrement}>-</button>{' '}
        <button onClick={this.incrementIfOdd}>increment if odd</button>{' '}
        <button onClick={this.incrementAsync}>increment async</button>
      </div>
    )
  }
}

Redux 版(相关用法)

在具体看代码前,先了解一下 Redux 的相关概念和用法。

之后如果突然对某步骤感到困惑,可参照该图记忆:
在这里插入图片描述
为了方便说明,在这里展示本例的项目结构:
在这里插入图片描述
redux 的三个核心概念:

(1)Action:

定义:Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch(action) 将 action 传到 store。( 但是需要注意,Action并没有直接修改 state,想要修改需要 Reducers)

而 dispatch 中的 action 应该需要包含两个方面的属性。

  1. type:用来表示将要执行的动作,其值为字符串,且是必要的唯一属性。多数情况下,type 会被定义成字符串常量。

  2. xxx:数据属性,值类型任意,是可选属性。

例:

store.dispatch({type: INCREMENT, data: number})

当应用规模越来越大时,建议使用单独的模块或文件来存放 action。因此在项目结构中创建 actions.js 和 action-types.js。综上所述,在 actions.js 中就是这样的内容:(这就是 Action Creators 模块:创建 Action 的工厂函数)

/*action creator模块*/
import {INCREMENT, DECREMENT} from './action-types'

export const increment = number => ({type: INCREMENT, number})
export const decrement = number => ({type: DECREMENT, number})

action-types.js 中是这样的内容:

/*Action对象的type常量名称模块*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

在这里的 action-types.js 是样板文件。像这样使用单独的模块或文件来定义 action type 常量并不是必须的,甚至根本不需要定义。对于小应用来说,使用字符串做 action type 更方便些。不过,在大型应用中把它们显式地定义成常量还是利大于弊的。

改进后,store.dispatch 这么用:

import * as actions from '../redux/actions'

store.dispatch(actions.increment(number)

也就是:我像 store 传递了一个 increment 标识 和 number 参数,之后 Reducers 就可以根据这个标识和参数,对原本的状态进行更新。

(2)Reducers:

定义:Reducers 指定了应用状态的变化如何响应 Action 并发送到 Store 的,即:根据原本的 State 和 Action,去产生新的 State。

reducers.js 中的内容:

/*根据原本的state和指定action, 处理返回一个新的state*/
import {INCREMENT, DECREMENT} from './action-types'

export function counter(state = 0, action) {
  console.log('counter', state, action)
  switch (action.type) {
    case INCREMENT:
      return state + action.number
    case DECREMENT:
      return state - action.number
    default:
      return state
  }
}

在这里,Reducers 也就是 counter,共有 2 个参数。其中 state 就是存储的状态,action 就是从 Actions 传递过来的内容。传递过来的内容如下:
在这里插入图片描述
我们可以看到,Reducers 部分 接收到了 type标识 和传递过来的参数。现在我们需要根据标识来对状态进行更新,但是在这里需要注意,虽然我们可以调用 state,但是不要修改原来的这个状态,需要通过 return 的方式返回一个新的状态,就像上面的代码所示。为了方便判断标识,才选择使用 switch case。

(3)Stores:

在上面的 Reducers 之所以能够接收到这些信息,是因为有 Store 的帮助。Store 是 redux 库最核心的管理对象。在它的内部维护着 状态 state 和 reducer。( 不要忘记之前在工作流程中提到,Store 不能直接修改状态,需要通过 Reducers )

如何得到此对象:

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

此时,Store,Actions,Reducers 就能够真正串联起来了。

Store 的核心方法:

store.getState() :得到 state

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

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


完整代码:

使用前:npm install --save redux

项目结构:
在这里插入图片描述
index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'

import App from './components/app'
import {counter} from './redux/reducers'

// 根据counter函数创建store对象
const store = createStore(counter)

// 定义渲染根组件标签的函数
const render = () => {
  ReactDOM.render(
    <App store={store}/>,
    document.getElementById('root')
  )
}
// 初始化渲染
render()

// 注册(订阅)监听, 一旦状态发生改变, 自动重新渲染
store.subscribe(render)

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, number})
export const decrement = number => ({type: DECREMENT, number})

reducers.js:

/*
根据老的state和指定action, 处理返回一个新的state
 */
import {INCREMENT, DECREMENT} from './action-types'

export function counter(state = 0, action) {
  console.log('counter', state, action)
  switch (action.type) {
    case INCREMENT:
      return state + action.number
    case DECREMENT:
      return state - action.number
    default:
      return state
  }
}

app.jsx:

/*
应用组件
 */
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import * as actions from '../redux/actions'

export default class App extends Component {

  static propTypes = {
    store: PropTypes.object.isRequired,
  }

  increment = () => {
    const number = this.refs.numSelect.value * 1
    this.props.store.dispatch(actions.increment(number))
  }

  decrement = () => {
    const number = this.refs.numSelect.value * 1
    this.props.store.dispatch(actions.decrement(number))
  }

  incrementIfOdd = () => {
    const number = this.refs.numSelect.value * 1

    let count = this.props.store.getState()
    if (count % 2 === 1) {
      this.props.store.dispatch(actions.increment(number))
    }
  }

  incrementAsync = () => {
    const number = this.refs.numSelect.value * 1
    setTimeout(() => {
      this.props.store.dispatch(actions.increment(number))
    }, 1000)
  }

  render() {
    return (
      <div>
        <p>
          click {this.props.store.getState()} times {' '}
        </p>
        <select ref="numSelect">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>{' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>
        {' '}
        <button onClick={this.incrementIfOdd}>increment if odd</button>
        {' '}
        <button onClick={this.incrementAsync}>increment async</button>
      </div>
    )
  }
}

其实 Redux 存在一些问题:

  1. Redux 与 React 组件的代码耦合度太高

  2. 编码不够简洁

为了解决这个问题,出现了 React-Redux。


React-Redux 版(相关用法)

React-Redux 是 React 的插件库。它专门用来简化 React 应用中使用 Redux。

使用 react-redux 下载依赖包:npm install --save react-redux

React-Redux 将所有组件分成两大类:

  1. UI 组件
    a. 只负责 UI 的呈现,不带有任何业务逻辑
    b. 通过 props 接收数据( 一般数据和函数 )
    c. 不使用任何 Redux 的 API
    d. 一般保存在 components 文件夹下

  2. 容器组件
    a. 负责管理数据和业务逻辑,不负责 UI 的呈现
    b. 使用 Redux 的 API
    c. 一般保存在 containers 文件夹下

综上所述,我们先创建项目结构:
在这里插入图片描述
action-types.js 、actions.js、reducers.js 和之前没有区别。

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
/*
action creator模块
 */
import {INCREMENT, DECREMENT} from './action-types'

export const increment = number => ({type: INCREMENT, number})

export const decrement = number => ({type: DECREMENT, number})
import {INCREMENT, DECREMENT} from './action-types'

export function counter(state = 0, action) {
  console.log('counter', state, action)
  switch (action.type) {
    case INCREMENT:
      return state + action.number
    case DECREMENT:
      return state - action.number
    default:
      return state
  }
}

接下来看 React-Redux 到底做了什么简化。

index.js

相比于 Redux 版本,现在不需要注册监听 store.subscribe(render),但是需要使用 Provider:

<Provider store={store}>
	<App />
</Provider>

这将能够让所有组件都可以得到 state 数据

完整代码:

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'

import App from './containers/app'
import {counter} from './redux/reducers'

// 根据counter函数创建store对象
const store = createStore(counter)

// 定义渲染根组件标签的函数
ReactDOM.render(
  (
    <Provider store={store}>
      <App/>
    </Provider>
  ),
  document.getElementById('root')
)

app.jsx

对于 app.jsx,在这里它是容器组件,负责管理数据和业务逻辑,不负责 UI 的呈现。在这里会用到 connect。它专门用于包装 UI 组件生成容器组件。用法如下:

import { connect } from 'react-redux'
connect(
	mapStateToprops,
	mapDispatchToProps
)(Counter)

在这里,mapStateToprops 处就可以将外部的数据(即 state 对象)转换为 UI 组件的标签属性;mapDispatchToProps 处就可以将分发 action 的函数转换为 UI 组件的标签属性。然后将这些信息传递给 Counter。

完整代码:

/*
包含Counter组件的容器组件
 */
import React from 'react'
// 引入连接函数
import {connect} from 'react-redux'
// 引入action函数
import {increment, decrement} from '../redux/actions'

import Counter from '../components/counter'

// 向外暴露连接App组件的包装组件
export default connect(
  state => ({count: state}),
  {increment, decrement}
)(Counter)

counter.jsx

随后,在 UI 组件 counter.jsx 中 就可以只负责 UI 的呈现,不带有任何业务逻辑,直接通过 props 接收数据。

完整代码:

/*
UI组件: 不包含任何redux API
 */
import React from 'react'
import PropTypes from 'prop-types'

export default class Counter extends React.Component {

  static propTypes = {
    count: PropTypes.number.isRequired,
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired
  }

  increment = () => {
    const number = this.refs.numSelect.value * 1
    this.props.increment(number)
  }

  decrement = () => {
    const number = this.refs.numSelect.value * 1
    this.props.decrement(number)
  }

  incrementIfOdd = () => {
    const number = this.refs.numSelect.value * 1
    let count = this.props.count
    if (count % 2 === 1) {
      this.props.increment(number)
    }
  }

  incrementAsync = () => {
    const number = this.refs.numSelect.value * 1
    setTimeout(() => {
      this.props.increment(number)
    }, 1000)
  }

  render() {
    return (
      <div>
        <p>
          click {this.props.count} times {' '}
        </p>
        <select ref="numSelect">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>{' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>
        {' '}
        <button onClick={this.incrementIfOdd}>increment if odd</button>
        {' '}
        <button onClick={this.incrementAsync}>increment async</button>
      </div>
    )
  }
}

现在只剩下一个问题,Redux 默认是不能进行异步处理的,但是在我们的例子中又需要在 Redux 中执行异步任务(ajax, 定时器),所以 Redux 的异步编程就出现了。


Redux 异步编程

使用前需要下载 Redux 插件(异步中间件)npm install --save redux-thunk

index.js 中,首先需要应用上异步中间件。这里需要使用 applyMiddleware() ,它的作用就是应用上基于 redux 的中间件 ( 插件库 ),在这里就是应用 thunk。

import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'

import App from './containers/app'
import {createStore, applyMiddleware} from "redux";
import thunk from 'redux-thunk'
import reducers from "./redux/reducers";

// 根据counter函数创建store对象
const store = createStore(
    reducers,
    applyMiddleware(thunk) // 应用上异步中间件
)
// 定义渲染根组件标签的函数
ReactDOM.render(
  (
    <Provider store={store}>
      <App/>
    </Provider>
  ),
  document.getElementById('root')
)

随后将异步代码放入 action 中。

在这里需要注意,同步的action只需要返回一个对象,而对于异步的action需要返回一个函数。

/*action creator模块*/
import {INCREMENT, DECREMENT} from './action-types'

export const increment = number => ({type: INCREMENT, number})

export const decrement = number => ({type: DECREMENT, number})

// 异步action creator(返回一个函数)
export const incrementAsync = number => {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment(number))
    }, 1000)
  }
}

然后将 action 传递给 UI组件。

/*
包含Counter组件的容器组件
 */
import React from 'react'
// 引入连接函数
import {connect} from 'react-redux'
// 引入action函数
import {increment, decrement, incrementAsync} from '../redux/actions'
import Counter from '../components/counter'

// 向外暴露连接App组件的包装组件
export default connect(
  state => ({count: state.counter}),
  {increment, decrement, incrementAsync}
)(Counter)

之后去 UI 组件中调用。

  incrementAsync = () => {
    const number = this.refs.numSelect.value*1
    this.props.incrementAsync(number)
  }

其它用法

Reducers 中会不止一个状态,此时为了方便创建 Store 对象,我们可以使用 combineReducers(),它用来合并多个 reducer 函数。

import {combineReducers} from 'redux'
// ....
export default combineReducers({
  user, chatUser, chat
})
import {createStore} from "redux";
import reducers from "./redux/reducers";
// 创建store对象
const store = createStore(reducers)

在 chrome 浏览器上 使用 redux 调试工具,首先需要为浏览器下载 redux-devtools_2_15_1.crx 。随后在项目中还要npm install --save-dev redux-devtools-extension。代码:

import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(
	counter,
	composeWithDevTools(applyMiddleware(thunk))
)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只爭朝夕不負韶華

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值