React 之 redux 的使用方法详解

React 之 redux 的使用方法

    • 介绍
    • 概念
      • store
      • reducer
      • action
    • 实现共享
    • 总结

介绍

  首先,redux是基于Flux思想的一种比较流行的状态管理工具,状态管理的本质:就是对应用程序的数据进行科学地流程化管理,目标是让数据变化可预期、可控(状态可以理解为 数据 ),而在开发项目中,它不是必须的。但如果开发大型项目应用,我们往往考虑的是如何高效的管理组件数据,那么redux就是一种很不错的选择。就像 Dan Abramov(丹•阿布拉莫夫) 说的一样:

Flux 架构就像眼镜:您自会知道什么时候需要它。

  其次,安装:

// 作用:创建store的
cnpm install redux -S

// 作用:把redux和react组件关联起来
cnpm install react-redux -S

  OK,下面让我们来了解一下redux的一些基本概念

先让我们来看下该项目的目录以及 src 目录下的目录结构:
React 之 redux 的使用方法详解_第1张图片
React 之 redux 的使用方法详解_第2张图片

概念

store

  在我们使用脚手架搭建项目的时候默认是没有store的,它是由我们手动在项目的src目录下创建的一个保存数据的store文件夹。redux提供了createStore这个官方API用来生成store

首先,着重一下 store 的三大原则:

  • 单一数据源:整个应用的 state 被储存在一棵 object 树中,并且这个 object 树只存在于唯一一个 store 中;
  • State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象;
  • 使用纯函数来执行修改:为了描述 action 如何改变 state 树 ,需要定义 reducers。

  为此,在 store 文件夹的目录下,我们再创建一个 index.js 文件。

===============index.js===================

// 引入相应的api
import { createStore, combineReducers, applyMiddleware } from 'redux'

// 引入 子reducer
import testRreducer from './reducers/test'

// 引入 redux-thunk ,使得被 dispatch 的 function 会接收 dispatch 作为参数,并且可以异步调用它
import thunk from 'redux-thunk'

// 将多个子 reducer 合并成一个 reducer
let reducer = combineReducers({
  test: testRreducer,
})
// 创建一个 store
let store = createStore(reducer, applyMiddleware(thunk))
// 导出
export default store

  使用 store,在App.js主页面中引入store,并使用上下文进行关联,用共享数据包含需要用到共享数据的组件;

============== App.js ============
== 简述 ,后面有更完整的 App.js 代码 ==
import { Provider } from 'react-redux'
import store from '@/store'
// 在render生命周期中
render(){
	return (
		<Provider store={store}>
        	<Home></Home>
        </Provider>
	)
}

  从上述代码中,我们看到了从 redux 中还引入了另外的两个 APIcombineReducersapplyMiddleware

  combineReducers:随着应用数据变得越来越复杂,我们可以考虑将 reducer 函数拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。而 combineReducers 辅助函数的作用是把一个由多个不同 reducer 函数作为值的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

  applyMiddleware:redux 默认只支持同步的 action,applyMiddleware 该 API 为 createStore 注入了 middleware(中间件),而我们引入的 redux-thunk 是用于基本 Redux 逻辑的推荐中间件,包括需要访问存储的复杂同步逻辑和像 AJAX 请求这样的简单异步逻辑。简单来说,就是我们引入 redux-thunk 作为中间件,使的被 dispatchfunction 会接收 dispatch 作为参数,且可以异步调用它;在代码中我们可以简单的理解为 redux-thunk 把一个异步的 action 转化成三个同步的 action,来解决 redux 只支持同步的 action 的特点;
这三个同步的action分别是:

  • 第一个 action 的作用是告诉 reducer 有一个异步行为触发了;
  • 第二个 action 的作用是表示异步行为成功了;
  • 第三个 action 的作用是告诉 reducer 这个异步行为失败了。

  在运行之前不要忘记安装该中间件:

cnpm install redux-thunk -S

reducer

  从目录结构以及代码中我们看到了reducer,那么reducer是做什么的呢?
  简单来说reducer是一个纯函数,什么是纯函数?就是相同的输入必定有相同的输出,就叫做纯函数。reducer 作用就是用来改变 store 中的数据。定义reducer需要两个参数,分别是当前需要被共享的state以及用于改变state的action。

================reducers中的 test.js ==============

import { CHANGE_MSG} from '../actionTypes'
import { CNODE_LIST } from '../actionTypes'
// 定义初始数据
let initState = {
  msg: 'hello redux',
  cnodeList: []
}
// reducer 是概念之一,它是一个纯函数,用来改变数据的纯函数
// 这个纯函数有两个参数,参数1:被共享的数据,参数2:用来改变数据的action
export default function reducer(state=initState, action){
  switch (action.type) {
    case CHANGE_MSG:
      console.log('它带着action来了', action)
      // 将state进行深复制
      let newState = JSON.parse(JSON.stringify(state))
      newState.msg = action.payload
      return newState  
    case CNODE_LIST:
      return {...state, ...{cnodeList: action.payload}}
    default:
      return state
  }

  

action

  那么action又是什么呢?action我们可以理解为是一种改变数据的触发行为,作用就是通知reducer改变哪一条数据;

  从目录结构中可以看到src目录下还有一个actionTypes的js文件,该文件的作用就是将actions文件夹中定义的关于action的文件进行统一管理;

==================actionTypes.js==============

// 把整个应用程序中所有的 action type 都写在这里
// 可以保证 action type 永远不会重复、冲突
export const CHANGE_MSG = 'CHANGE_MSG'
// 如果还有其他数据继续定义并导出
// 示例:
// cnode
export const CNODE_LIST = 'CNODE_LIST'

actions文件夹中定义的关于action的js文件中,有若干个数据就有若干个action的js文件

=============actions 中的 test.js==================

import { getCnodeList } from '@/utils/api'
import { CNODE_LIST } from '../actionTypes'
// 封装action,是为了代码复用
export function changeMsg(payload){
  return {
    type: 1,
    payload
  }
}
// 使用redux-thunk 将一个异步的action转化成三个同步的action
// 第一个action 的作用是告诉reducer有一个异步行为触发了
// 第二个action 的作用是告诉reducer异步行为成功了
// 第三个action 的作用是告诉reducer异步行为失败了
export function cnode(params){
  return function(dispatch){
    getCnodeList(params).then( res => {
      // 第二个action,这是成功的action
      dispatch({
        type: CNODE_LIST,
        payload: res
      })
    }).catch( err => {
      // 第三个action,这是失败的action
      dispatch({ 
        type: CNODE_LIST,
        payload: '失败了,'+ err
      })
    })
  }
}

  

实现共享

  那么接下来就需要在“页面”组件中使用共享的数据了;

比如其中的一个Home页面组件:

=================Home.js===============

import React from 'react'

import { connect } from 'react-redux'
import { changeMsg, cnode } from '@/store/actions/test'

// 定义函数,将state中的数据变成当前组件的props
function mapStateToProps(state){
  return {
    msg: state.test.msg,
    cnodeList: state.test.cnodeList
  }
}
// 定义函数,将actions中的方法也赋给当前组件的props
function mapActionToProps(dispatch){
  return {
    xxx: function(payload){
      return dispatch(changeMsg(payload))
    },
    getCnodeList: (params) => dispatch(cnode(params)) // 这里是发送第一个aciton,也就是告诉reducer有一个异步行为触发了
  }
}

class Home extends React.Component{
  // constructor(props){
  //   super(props)
  // }
  click(){
    this.props.xxx('hello 12345')
  }
  componentDidMount(){ // 在redux可直接在componentDidMount生命周期中请求接口,且不和mobx一样,这里只会请求一次
    let params = {
      page: 1,
      limit: 5,
      tab: ''
    }
    this.props.getCnodeList(params)
  }
  createCnodeList(){
    return this.props.cnodeList.map( ele => (
      <div key={ele.id}>{ele.title}</div>
    ))
  }
  render(){
    return (
      <div>
        <h2>首页</h2>
        <h1>{this.props.msg}</h1>
        <button onClick={this.click.bind(this)}>修改msg</button>
        <hr/>
        <div>
          {this.createCnodeList()}
        </div>
      </div>
    )
  }
}
export default connect(mapStateToProps, mapActionToProps)(Home)

在 views 目录下的统一出口文件 index.js

================= index.js =====================

import loadable from '@loadable/component'
// 路由懒加载模式
const Home = loadable( () => import('./home/Home'))
export default [
	{
        id: 101,
        path: '/',
        component: Home,
        text: '首页'
      }
]

主页面组件App.js

============== App.js ===================

import React from 'react';
// 引入路由
import { HashRouter, NavLink, Route, Switch, Redirect } from 'react-router-dom'
// 引入侧边导航的数据
import routes from '@/views/'
// redux相关
import { Provider } from 'react-redux'
import store from '@/store/'

export default class App1 extends React.Component {
  constructor(props){
    super(props)
    this.state = {

    };
  }
  // 创建侧边导航
  createNavLink(){
    return routes.map( ele => (
      <div key={ele.id}>
        <NavLink to={ele.path}>{ele.text}</NavLink>
      </div>
    ))
  }
  // 创建content
  createConentList(){
    let arr = []
    routes.map( ele => {
          arr.push(
            <Route 
              key={ele.id}
              path={ele.path}
              component={ele.component}
              exact
            >
            </Route>
          )
          return false
        })
    return arr
  }
  render(){
    return (
      <HashRouter>
        <Provider store={store}>
          <div>
            {this.createNavLink()}
          </div>
          <div>
            <Switch>
              {this.createConentList()}
            </Switch>
            <Redirect from='/*' to='/good'></Redirect>
          </div>
        </Provider>
      </HashRouter>
    )
  }
}

  

总结

  最后,让我们来捋捋 redux 的流程;
  store 中的数据通过 reducer 来修改,那么需要定义 reducer,定义 reducer 需要两个参数,参数 1 为当前需要被共享的数据,参数 2 为用于改变数据的 action,从而引出 action,action 就是触发改变 store 数据的一种行为或者说是信号,在 action 中定义一个改变数据的行为(函数),该函数中 return 一个 action 对象,该对象也就是定义 reducer 中的参数2 action,接着在 reducer 中一般通过 switch 语句判断 action 对象的 type 属性来改变数据,然后在需要用到共享数据的组件中用 Provider 组件使用 store 包裹,那么触发了行为(action 中定义的函数),与 reducer 中的参数 2 action的 type (信号)相对应、相匹配,进而改变视图。

store 的三大原则:

  • 单一数据源:整个应用的 state 被储存在一棵 object 树中,并且这个 object 树只存在于唯一一个 store 中;
  • State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象;
  • 使用纯函数来执行修改:为了描述 action 如何改变 state 树 ,需要定义 reducers。

你可能感兴趣的:(React)