zoukankan      html  css  js  c++  java
  • 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最终的目的其实也只有一个,就是写更少的代码做更多的事情。

  • 相关阅读:
    新人优惠的风险
    Linux服务器记录并查询历史操作记录
    斜率比截距重要
    专访李果:初生牛犊不怕虎的移动创业者
    iPhone开发视频教程 ObjectiveC部分 (51课时)
    珍藏40个android应用源码分享
    iPhone开发教程 UI基础课程(58课时)
    ios源码分享之动画类
    史上最全的ios源码汇总。欢迎收藏
    Android开发教程汇总
  • 原文地址:https://www.cnblogs.com/zyx-blog/p/9294991.html
Copyright © 2011-2022 走看看