React项目开发中的数据管理

原文链接:https://blog.csdn.net/hl582567508/article/details/76982756

redux中文文档:http://cn.redux.js.org/

React项目开发中的数据管理

  对于React的初学者在项目开发中常常还是会以DOM操作的思维方式去尝试获取、修改和传递数据,但是这种思想,在React思想中显然是错误的,针对这种情况下文将进行一个简易的总结。我们将从基础的纯React组件之间的传值开始论述,然后分析React结合Redux之间的数据传递,以及最后基于dva脚手架的数据传输问题进行探讨和分析。

一、 原生React组件的数据传输管理:

  原生React组件之间的数据传输主要依赖于两个关键词:属性(props) 和状态(state)。每一个组件都是一个对象,props是对象的一个属性,组件对象可以通过props进行传递。React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。state与props的最大区别在于props是不可变的而state是可变的。具体内容后面会详细讲解。

原生React组件之间数据传递场景可以分为以下四种: 
- 组件内部的数据传输 
- “父组件”向“子组件”传值 
- “子组件”向 “父组件”传值 
- “兄弟组件”之间的传值

  1. 组件内部的数据传输

  在初学过程的项目开发中常常会有去尝试DOM操作的冲动,虽然大部分情况下这种尝试是错误的,但是在某些时候还是不得不需要获取对DOM的值进行操作。例如:点击一个按钮之后触发一个点击事件,让一个input文本框获得焦点。jQuery开发者的第一反应肯定是给button绑定点击事件,然后在事件中通过$(‘select’)获取到要操作的节点,再给节点添加焦点。然而在React中这种操作是不允许的,而React中应该怎么做呢?

React Refs属性:

import React, { Component } from 'react';

class MyComponent extends Component({
  handleClick = () => {
    // 使用原生的 DOM API 获取焦点
    this.refs.myInput.focus();
  },
  render: function() {
    //  当组件插入到 DOM 后,ref 属性添加一个组件的引用于到 this.refs
    return (
      <div>
        <input type="text" ref="myInput" />
        <input
          type="button"
          value="点我输入框获取焦点"
          onClick={this.handleClick}
        />
      </div>
    );
  }
});

ReactDOM.render(
  <MyComponent />,
  document.getElementById('example')
);

  我们可以从上面的代码当中看到 其中的ref在功能上扮演起了一个标识符(id)的角色,this.refs.myInput.focus()也有一种document.getElementById(‘myInput’).focus()的味道。

  上面的操作我们也称为React表单事件,React表单事件中除了ref具有关键作用外,还有另一个关键参数’event’。例如:当我需要实时获取到一个文本框里面的内容,然后进行一个判断,当满足某个条件的时候触发另一个事件。这个时候就需要使用到这个一个关键参数’event’。

React 表单事件-event参数:


class MyComponent extends Component{
    handleChange = (event) => {
        if(event.target.value === 'show'){
            console.log(this.refs.showText);
        }
    };
    render(){
        return(
            <div>
                <input type="text" onChange={this.handleChange}/>
                <p ref='showText'>条件满足我就会显示在控制台</p>
            </div>

        )

    }
}
export default MyComponent;

  上面实例实现的效果就是,通过event.target.value获取当前input中的内容,当input中输入的内容是show的时候,控制台就将ref为showText的整个节点内容打印出来。从这个实例当中我们也看到了,event作为一个默认参数将对应的节点内容进行了读取。

  因此在组件内部涉及的DOM操作数据传递主要就是这两种方式,可以根据不同的场景选择不同的方式。虽然ref适用于所有组件元素,但是ref在正常的情况下都不推荐使用,后面会进行介绍通过 state管理组件状态,避免进行DOM的直接操作。

  1. “父组件”向“子组件”传值

  父组件与子组件之间的通信通常使用props进行。具体如下:


import React,{ Component } from 'react'

class ChildComponent extends Component{
    render (){
        return (
            <div>
                <h1>{this.props.title}</h1>
                <span>{this.props.content}</span>
            </div>
        )
    }
}
class ParentComponent extends Component {
    render (){
        return (
            <div>
                <ChildComponent title="父组件与子组件的数据传输测试" content="我是传送给子组件span中显示的数据" />
                <p>我是父组件的内容</p>
            </div>
        )
    }
}

export default ParentComponent;

上面示例展示了父组件向子组件传递了两个props属性分别为title和content,子组件通过this.props获取到对应的两个属性,并将其展示出来,这个过程就是一个父与子组件之间的数据交互方式。但是也可以从例子中看到props的值是不变的,父传给子什么样的props内容就只能接收什么样的使用,不能够在子中进行重新赋值。

  1. “子组件”向 “父组件”传值

  本例中将会引入了管理组件状态的state,并进行初始化。具体如下:

import React, { Component } from 'react';
//子组件
class Child extends Component {
    render(){
        return (
            <div>
                请输入邮箱:<input onChange={this.props.handleEmail}/>
            </div>
        )
    }
}
//父组件,此处通过event.target.value获取子组件的值
class Parent extends Component{
    constructor(props){
        super(props);
        this.state = {
            email:''
        }
    }
    handleEmail = (event) => {
        this.setState({email: event.target.value});
    };
    render(){
        return (
            <div>
                <div>用户邮箱:{this.state.email}</div>
                <Child name="email" handleEmail={this.handleEmail}/>
            </div>
        )
    }
}

export default Parent;

通过上面的例子可以看出”子组件”传递给”父组件”数据其实也很简单,概括起来就是:react中state改变了,组件才会update。父写好state和处理该state的函数,同时将函数名通过props属性值的形式传入子,子调用父的函数,同时引起state变化。子组件要写在父组件之前。

从本示例中也可以看出state可以通过setState进行重新赋值,因此state是可变的,表示的是某一时间点的组件状态。

  1. “兄弟组件”之间的传值

  当两个组件不是父子关系,但有相同的父组件时,将这两个组件称为兄弟组件。严格来说实际上React是不能进行兄弟间的数据直接绑定的,因为React的数据绑定是单向的,所以才能使得React的状态处于一个可控的范围。对于特殊的应用场景中,可以将数据挂载在父组件中,由两个组件共享:如果组件需要数据渲染,则由父组件通过props传递给该组件;如果组件需要改变数据,则父组件传递一个改变数据的回调函数给该组件,并在对应事件中调用。从而实现兄弟组件之间的数据传递。

import React, { Component } from 'react';
//子组件
class Child extends Component {
    render(){
        return (
            <div>
                我是子组件邮箱:<input onChange={this.props.handleEmail} defaultValue={this.props.value} />
            </div>
        )
    }
}
//兄弟组件
class ChildBrother extends Component {
    render(){
        return (
            <div>
                我是兄弟组件:{this.props.value}
            </div>
        )
    }
}
//父组件,此处通过event.target.value获取子组件的值
class Parent extends Component{
    constructor(props){
        super(props);
        this.state = {
            email:''
        }
    }
    handleEmail = (event) => {
        this.setState({email: event.target.value});
    };
    render(){
        return (
            <div>
                <div>我是父组件邮箱:{this.state.email}</div>
                <Child handleEmail={this.handleEmail} value={this.state.email}/>
                <ChildBrother value={this.state.email}/>
            </div>
        )
    }
}

export default Parent;

上面例子中就是child组件的值改变后存储在父组件的state中,然后再通过props传递给兄弟组件childBrother。从而实现兄弟组件之间的数据传递。

二、 基于Redux的React项目开发中的数据管理

  前面在分析原生React组件之间的数据传输中讲到两个关键词:state和props,在项目的实际开发过程中,这里的state可能包括服务器响应数据、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

  管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。

因此在这些问题下便产生了 Redux ,在Redux的理念中通过限制更新发生的事件和方式试图让state的变化变的可预测。Redux可以用三个基本原则来描述:

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

redux的基本工作流程为store进行管理state和reducers,reducers接收一个action和原始的state,生成一个新的state,dispatch进行触发一个action,打一个比方:store就好比是一个银行,state就是银行中存的钱,reducers就是银行的用户管理系统,dispatch就是取款机,action就是取款机发出的请求,component就是用户。所以当我们要完成一个取钱的过程,首先就是用户(component)通过取款机(dispatch)发起一个(action)取款的请求,当银行的用户管理系统(reducers)接收到请求以后,调取用户的原来的账户信息(old state),进行相应(action)操作,如果没有什么问题则更改账户信息生成新的账户资料(new state),并把钱取给用户(返回给component)。

整个流程可以通过下图表示:

这里写图片描述

我们可以来看一个简单的例子:基本功能就是在一个任务管理器中添加新的任务,我们主要看其数据走向。

Action:

let nextTodoId = 0
export const addTodo = (text) => ({
  type: 'ADD_TODO',
  id: nextTodoId++,
  text
})

Reducers:

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    default:
      return state
  }
}

export default todos

Component:

import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        <input ref={node => {
          input = node
        }} />
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo

Store:

import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import reducer from './reducers'

const store = createStore(reducer)

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

上面示例就是一个简易的react-redux项目中的数据请求与处理,component发起dispatch(addTodo(input.value))请求,reducers接收’ADD_TODO’返回一个新的state,store进行管理整个reducers和state将其结果渲染在页面当中。

总结:

  redux只是对于react的state进行了管理,对于react的props并没有进行管理,这也与props本身的特性有关,props本身就是只读属性,所以可控性比较强,不需要进行再次包装管理。前面讲的主要是针对于同步情况下的redux的请求与处理过程,并没有阐述异步情况,由于其基本思想是一样的只是异步请求需要使用redux-saga的fetch请求远程服务器,然后再接收收据后进行相应的操作。具体的流程会在后面的基于dva的React项目开发中的数据管理中进行讲解。

三、 基于dva的React项目开发中的数据管理

  dva是基于 redux、redux-saga 和 react-router@2.x 的轻量级前端框架。是使用React技术栈进行前端开发的脚手架。

dva实际上并没有引入什么新的概念,依旧使用的是React、Redux、React-route技术栈的相关概念,唯一的特点就是简化了React和Redux、Redux-saga之间的数据数据交互。可以从下面的实例中来进行简要了解:

models:

import { getInputOutputProfiles,deleteInputOutputProfiles,addInputOutputProfiles } from "../../services/InputOutputManagement"

export default {
  namespace : 'input_output',
  state : {

    ...

    data:[],

    ...

  },
  effects : {
    *getInputOutputProfiles({ payload }, { put, call, select }) {
      const type_input='REMOTEFILESHARED_INPUT';
      const type_output='REMOTEFILESHARED_OUTPUT';
      const token = yield select(state => state.home.token);
      const result_input = yield call(getInputOutputProfiles,{payload:{token,type:type_input}});
      const result_output = yield call(getInputOutputProfiles,{payload:{token,type:type_output}});
      let data=[];
      result_input.remoteFileList.map(value => {
        data.unshift(value)
      });
      result_output.remoteFileList.map(value => {
        data.unshift(value)
      });
      console.log(data);
      yield put({type:'setData',payload:{ data: data }});
    },

    ...

    }
  },
  reducers : {

    ...

    setData(state,{ payload:{data} }){
      return { ...state, data:data }
    },

    ...

  },
  subscriptions : {
    setup({dispatch, history}) {
      return history.listen(({pathname}) => {
        if (pathname === '/system/input_output') {
          dispatch({
            type:'getInputOutputProfiles'
          });
        }
      });
    }
  }
}

component:

class SectionPanel extends Component{
 ...

 handleSubmit = () => {
    this.props.dispatch({ type: 'input_output/addInputOutputProfiles', payload: { id:this.props.id, type:this.props.type }});
  };

 ...
}

const mapStateToProps = ( state ) => {
  return state;
};

export default connect(mapStateToProps)(SectionPanel);

 从这个示例当中我们可以看到,model中进行state的定义,以及reducers的定义,在dva中reducers是唯一可以改变state的地方。从例子中我们可以看到,在subscriptions中进行了一个订阅监听,当加载pathname === ‘/system/input_output’的时候通过dispatch发起一个异步请求getInputOutputProfiles,请求会连接到服务器,从服务器端获取相应的数据,然后再对数据进行处理,再执行reducers中的同步setData:yield put({type:’setData’,payload:{ data: _temp }});改变当前state中的data数据。有了data数据,组件就可以遍历数据呈现给用户。

 这是一个由订阅数据源而发起的一个改变state的方式,除此之外,state改变和去向主要应用在组件当中,如上component当中所示,组件中需要使用state,首先要进行state和props的映射,然后组件就可以通过this.props进行获取相应的state值,因此通过mapStateToProps方法进行映射,然后通过connect方法将映射的结果与组件绑定,此处需要知道的是组件中发起请求的dispatch也是需要将组件与redux连接(connect)之后才能在组件中使用dispatch。这些准备工作做好之后便可以在组件中发起dispatch请求改变state状态了。

 从上面的示例中我们会发现在dva中不需要显式的编写action,也不用写创建store的过程,而是在dispatch中将传递action名改变为对象,对象包含两个部分{ type:”,payload:{ } },具体触发reducers的过程以及生成新的state的具体操作都是由dva内部进行,从而简化了操作。

以上便是一个dva项目的数据传递流,下面我以图的形式进行展示:

这里写图片描述

总结

 从原生React到react-redux再到dva其思想上实际并没有本质上的颠覆,redux简化react的数据管理,dva简化react-redux项目的数据管理,dva最终的目的其实也只有一个,就是写更少的代码做更多的事情。

原文地址:https://www.cnblogs.com/zyx-blog/p/9294991.html