原文連接:https://blog.csdn.net/hl582567508/article/details/76982756前端
redux中文文檔:http://cn.redux.js.org/node
對於React的初學者在項目開發中經常仍是會以DOM操做的思惟方式去嘗試獲取、修改和傳遞數據,可是這種思想,在React思想中顯然是錯誤的,針對這種狀況下文將進行一個簡易的總結。咱們將從基礎的純React組件之間的傳值開始論述,而後分析React結合Redux之間的數據傳遞,以及最後基於dva腳手架的數據傳輸問題進行探討和分析。react
原生React組件之間的數據傳輸主要依賴於兩個關鍵詞:屬性(props) 和狀態(state)。每個組件都是一個對象,props是對象的一個屬性,組件對象能夠經過props進行傳遞。React 的核心思想是組件化的思想,應用由組件搭建而成,而組件中最重要的概念是State(狀態),State是一個組件的UI數據模型,是組件渲染時的數據依據。state與props的最大區別在於props是不可變的而state是可變的。具體內容後面會詳細講解。redux
原生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-router
上面的操做咱們也稱爲React表單事件,React表單事件中除了ref具備關鍵做用外,還有另外一個關鍵參數’event’。例如:當我須要實時獲取到一個文本框裏面的內容,而後進行一個判斷,當知足某個條件的時候觸發另外一個事件。這個時候就須要使用到這個一個關鍵參數’event’。框架
React 表單事件-event參數:dom
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。從而實現兄弟組件之間的數據傳遞。
前面在分析原生React組件之間的數據傳輸中講到兩個關鍵詞:state和props,在項目的實際開發過程當中,這裏的state可能包括服務器響應數據、緩存數據、本地生成還沒有持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。
管理不斷變化的 state 很是困難。若是一個 model 的變化會引發另外一個 model 變化,那麼當 view 變化時,就可能引發對應 model 以及另外一個 model 的變化,依次地,可能會引發另外一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在何時,因爲什麼緣由,如何變化已然不受控制。
所以在這些問題下便產生了 Redux ,在Redux的理念中經過限制更新發生的事件和方式試圖讓state的變化變的可預測。Redux能夠用三個基本原則來描述:
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是基於 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最終的目的其實也只有一個,就是寫更少的代碼作更多的事情。