【React】浅析Redux

Redux在我看来,是和Vuex一样的存在,是为了跳出继承传值的条框,方便组件之间通信的一个方法。同时,它也有自己触发的体系。
首先还是看看Redux的流程图吧:
在这里插入图片描述
我用文字,描述一下需要用到的API以及经过的点:
1、ReactComponents:发起action 和 接收store的组件,组件发起action用store.dispath(),接收store返回的信息可以用store.getState()。如果想要实时订阅store变化返回的值,可以用store.subscribe(fn),其中,store是通过Redux中的createStore创建的。
2、store:中转站,转发组件传过来的action给Reducer,接收Reducer返回的state,并在返回的state有更新的时候实时更新并发给有实时订阅的组件
3、Reducers:处理state和action的地方根据不同的action,返回不同的store。

再直白点说,就是组件将改变,(通过actionCreators)封装成action通过dispath传给store,再由store传给Reducer,但是Redecer怎么知道如何处理数据呢?通过传入的action,判断该如何处理数据。处理好后返回给store,再由store派发给订阅的组件,组件再实时更新。

使用Redux,首先需要安装Redux插件:

npm install redux
//or
yarn add redux

建议将流程在心里熟悉之后再看demo,这里我准备的demo 是一个todoList,为了看起来美观点用了antd简单布局了一下,这里要实现的效果有,input框键入数值的时候实时更新,点击提交按钮的时候,list会添加键入数值,如果点击该list的项,会删除对应的项。

在这里插入图片描述
代码部分,组件:

import React, { Component } from 'react';
import { Input, Button, List } from 'antd';
import { ArrowRightOutlined } from '@ant-design/icons'
import store from './store'
import './app.css'

class App extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState()
    this.handleStoreChange = this.handleStoreChange.bind(this)
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleButtonClick = this.handleButtonClick.bind(this)
    store.subscribe(this.handleStoreChange)
  }
  handleStoreChange() {
    this.setState(store.getState())
  }
  handleInputChange(e) {
    const action = {
      type: 'handle_input_change',
      value: e.target.value
    }
    store.dispatch(action)
  }
  handleButtonClick() {
    const action = {
      type: 'handle_button_click'
    }
    store.dispatch(action)
  }
  handleItemClick(index) {
    const action = {
      type: 'handle_item_click',
      index
    }
    store.dispatch(action)
  }
  render() {
    return (
      <div>
        <div className={'total'}>
          <div className={'title'} >
            <div>
              <Input style={{ width: 525 }} value={this.state.inputValue} onChange={(e) => { this.handleInputChange(e) }} />
            </div>
            <div style={{ paddingLeft: 10 }}>
              <Button type="primary" onClick={() => { this.handleButtonClick() }}><ArrowRightOutlined style={{ color: 'white' }} /></Button>
            </div>

          </div>
          <div className={'list-area'}>
            <List
              className={'list ant-list'}
              bordered
              size='large'
              split='true'
              dataSource={this.state.list}
              renderItem={(item, index) => (
                <List.Item >
                  <div onClick={() => { this.handleItemClick(index) }}> {item}</div>
                </List.Item>
              )} />
          </div>

        </div>
      </div>
    )
  }
}

export default App

在这里,action是一个object,包括type和对应的value,type为必须项,value可选。该组件的state全靠reducer返回。
store我单独放的一个文件夹,在同级目录里,通过import引入功能。
store的代码:

import reducer from './reducer';
import { createStore } from 'redux';

const store = createStore(reducer);

export default store

通过Redux提供的createStoreAPI,建立一个store,为了可读性,我将store和reducer都单独建了一个文件夹,放在一起也是可以相互引用的。
reducer的代码:

const defaultState = {
  list: ['hello', 'world', 'React'],
  inputValue: '2222'
}

export default (state = defaultState, action) => {
  console.log('state:', state, 'action', action)
  let newstate = JSON.parse(JSON.stringify(state))
  switch (action.type) {
    case 'handle_input_change':
      newstate.inputValue = action.value
      return newstate
    case 'handle_button_click':
      newstate.list.push(newstate.inputValue);
      newstate.inputValue = ''
      return newstate
    case 'handle_item_click':
      newstate.list.splice(action.index, 1)
      return newstate
    default:
      return state
  }

}

其实reducer是一个纯函数,接收state和action,且这个函数不能直接修改state,修改state只能通过深拷贝,修改并返回备份。这里可以看到,我是通过判断action.type来做出相应的增删改的处理。
在reducer中,我将defaultState定义好了并传值,如果按照日常开发,这里应该是空,后续请求数据填充进去,这里需要注意一点,虽然是空,但是需要将需要用到的state的类型写入defaultState中,例如上面这个defaultState,应该写成:

const defaultState = {
  list: [],
  inputValue: ''
}

这一点和React组件不同,在组件里面,state可以不先定义在this.state中,只要不涉及到页面渲染的,可以用setState定义并赋值,如果涉及到页面渲染又没有定义,后续用setState赋值的,在赋值前会是undefind,不会报错,且无论网速多块,这个undefind都会闪一下。
至此,一个小的demo就做好了,但是仔细看流程图,action是被actionCreators封装发出的,但是我的代码中是组件自己封装好发出的,这个时候就存在问题:
1、如果代码复杂,那么散落在各处的action增加后期维护时间和精神成本;
2、actionType是通过创建action的时候手动打的字段,可能出现手打错误但是错误不明显,有了bug浏览器不会报错(不信可以自己实验一下),找的话也耗费时间,更没有头绪。这个时候推荐自己创建一个actionTypes统一规划actionType,如果引用出错,浏览器也会精准的报错。
根据这两个问题,我们优化一下代码结构,这个时候整体结构如下:
在这里插入图片描述
首先,我们集合一下actionTypes:

export const HANDLE_INPUT_CHANGE = 'handle_input_change'
export const HANDLE_BUTTON_CLICK = 'handle_button_click'
export const HANDLE_ITEM_CLICK = 'handle_item_click'

reducer那里也涉及到了actionTypes,相应做出更换,为了避免冗余,代码仅贴部分改动的部分:

 case HANDLE_INPUT_CHANGE:
      newstate.inputValue = action.value
      return newstate

然后,再将action全部提到actionCreator里面:

import { HANDLE_INPUT_CHANGE, HANDLE_BUTTON_CLICK, HANDLE_ITEM_CLICK } from './actionTypes'

export const inputChangeAction = (value) => ({
  type: HANDLE_INPUT_CHANGE,
  value
})

export const buttonClickAction = () => ({
  type: HANDLE_BUTTON_CLICK
})

export const itemClickAction = (value) => ({
  type: HANDLE_ITEM_CLICK,
  index: value
})

最后,我们修改组件,首先,头部引用actionCreator:

import { inputChangeAction, buttonClickAction, itemClickAction } from './actionCreator'

action处:

handleInputChange(e) {
    const action = inputChangeAction(e.target.value);
    store.dispatch(action)
  }

然后我们验证一下,我们新写一个组件b.js,在里面展示从Redux得到的数据:

import React from 'react'
import store from './store'

let data = store.getState()
class B extends React.Component {
  constructor(props) {
    super(props);
    this.getStateData = this.getStateData.bind(this);
    store.subscribe(this.getStateData)
  }
  getStateData = () => {
    data = store.getState()
  }

  render() {
    return (
      <div>接收到的数据是:{data.list}</div>
    )
  }
}

export default B;

页面的变化:
在这里插入图片描述
这里可以看到,只要是注册了store,就可以从里面得到当前state里的值(前提是用subscribe订阅了)。在实际使用中,可以跳出继承的限制,如果组件有数据更新,发出diapath后store更新,那么引用store里相应的值的组件也会发现相应的更新,这个功能其实非常的使用,比如在地区页选择了地区,相应的主页上会显示出相应的选择,等等。
Redux-thunk:
这是一个Redux的中间件,它的主要作用是将action从对象变成函数,可以处理异步函数,而Redux只能处理同步的数据。现在的工程中,异步函数的使用必不可少,所以Redux-thunk是个不得不提的中间件。且在action里可以引入dispath,更加方便管理。
首先,得安装这个中间件:

npm install --save redux-thunk
//or
yarn add redux-thunk

为了演示异步操作,我用express在本地的后端起了个服务,占用7001接口(另一个工程):

const express = require('express')

const app = express();

app.get('/api', (req, res) => {
  res.json(['learn', 'redux', 'thunk'])
})
app.listen(7001, () => {
  console.log('serverisrunning')
})

功能只有简单的返回数组,然后改一下前端工程的代理:

npm install http-proxy-middleware -D

因为我是react16,通过package.json改代理会出现报错,所以装一个中间件消除报错。
在src目录下新建一个setupProxy,内容:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:7001/api',
      pathRewrite: {
        "^/api": ""
      },
      changeOrigin: true,
    })
  );
};

如果觉得自己起服务然后引用过于复杂,可以用charles等抓包工具做代理,引入本地的json,但是最新版本的charles不支持。
背景介绍完毕,接下来我们引入redux-thunk,在store中引入并注册:

import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
const store = createStore(reducer, applyMiddleware(thunk));

到组件中,在componentDidMount生命周期中,加入:

const action = initData();
store.dispatch(action)

再到actionCreator中加入:

export const initDataAction = (value) => ({
  type: INIT_DATA_ACTIONS,
  value
})

export const initData = () => {
  return dispatch => {
    //这里做异步的操作
    axios.get('/api').then((res) => {
      const action = initDataAction(res.data)
      dispatch(action)
    })
  }
}

相应的,reducer:

 case INIT_DATA_ACTIONS:
      newstate.list = action.value
      return newstate

actionTypes:

export const INIT_DATA_ACTIONS = 'init_data_actions'

得到的效果如下:

在这里插入图片描述
如果有不正确的,欢迎指正,我及时修改。
详细的代码请见:https://github.com/PYLDora/React-Redux

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值