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組件之間數據傳遞場景可以分為以下四種:?
- 組件內部的數據傳輸?
- “父組件”向“子組件”傳值?
- “子組件”向 “父組件”傳值?
- “兄弟組件”之間的傳值
??在初學過程的項目開發中常常會有去嘗試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的直接操作。
??父組件與子組件之間的通信通常使用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內容就只能接收什么樣的使用,不能夠在子中進行重新賦值。
??本例中將會引入了管理組件狀態的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是可變的,表示的是某一時間點的組件狀態。
??當兩個組件不是父子關系,但有相同的父組件時,將這兩個組件稱為兄弟組件。嚴格來說實際上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
總結
以上是生活随笔為你收集整理的React项目开发中的数据管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: git fatal: 拒绝合并无关的历
- 下一篇: 机器学习实战3--豆瓣读书简介