前言:
本文分為2個部分:
1.前半部分為5個前置知識點,簡要介紹了React中的props和state、dva中connect膝迎、dispatch方法
2.后半部分從一個項目實例入手介紹react+dva前后端傳值的過程,涉及models介劫、action峰弹、view理卑、service距辆。
Tips:配合官網(wǎng)看例子效果更好:
react官網(wǎng):https://reactjs.org/
dva-github:dvajs/dva
1.React中的props和state的區(qū)別
參考:https://segmentfault.com/q/1010000008340434
在任何應(yīng)用中,數(shù)據(jù)都是必不可少的暮刃。我們需要直接的改變頁面上一塊的區(qū)域來使得視圖的刷新挑格,或者間接地改變其他地方的數(shù)據(jù)。React的數(shù)據(jù)是自頂向下單向流動的沾歪,即從父組件到子組件中漂彤,組件的數(shù)據(jù)存儲在props和state中,這兩個屬性有啥子區(qū)別呢灾搏?
props
React的核心思想就是組件化思想挫望,頁面會被切分成一些獨立的、可復(fù)用的組件狂窑。
組件從概念上看就是一個函數(shù)媳板,可以接受一個參數(shù)作為輸入值,這個參數(shù)就是props泉哈,所以可以把props理解為從外部傳入組件內(nèi)部的數(shù)據(jù)蛉幸。由于React是單向數(shù)據(jù)流,所以props基本上也就是從服父級組件向子組件傳遞的數(shù)據(jù)丛晦。
用法
假設(shè)我們現(xiàn)在需要實現(xiàn)一個列表奕纫,根據(jù)React組件化思想,我們可以把列表中的行當(dāng)做一個組件烫沙,也就是有這樣兩個組件:和匹层。
先看看
import Item from “./item”;
export default class ItemList extends React.Component{
const itemList = data.map(item => <Item item=item />);
render(){
return (
{itemList}
)
}
}
列表的數(shù)據(jù)我們就暫時先假設(shè)是放在一個data變量中,然后通過map函數(shù)返回一個每一項都是的數(shù)組锌蓄,也就是說這里其實包含了data.length個組件升筏,數(shù)據(jù)通過在組件上自定義一個參數(shù)傳遞。當(dāng)然瘸爽,這里想傳遞幾個自定義參數(shù)都可以您访。
具體是這樣的:
export default class Item extends React.Component{
render(){
return (
<li>{this.props.item}</li>
)
}
}
在render函數(shù)中可以看出,組件內(nèi)部是使用this.props來獲取傳遞到該組件的所有數(shù)據(jù)剪决,它是一個對象灵汪,包含了所有你對這個組件的配置,現(xiàn)在只包含了一個item屬性昼捍,所以通過this.props.item來獲取即可识虚。
只讀性
props經(jīng)常被用作渲染組件和初始化狀態(tài),當(dāng)一個組件被實例化之后妒茬,它的props是只讀的担锤,不可改變的。如果props在渲染過程中可以被改變乍钻,會導(dǎo)致這個組件顯示的形態(tài)變得不可預(yù)測肛循。只有通過父組件重新渲染的方式才可以把新的props傳入組件中铭腕。
默認(rèn)參數(shù)
在組件中,我們最好為props中的參數(shù)設(shè)置一個defaultProps多糠,并且制定它的類型累舷。比如,這樣:
Item.defaultProps = {
item: ‘Hello Props’,
};
Item.propTypes = {
item: PropTypes.string,
};
關(guān)于propTypes夹孔,可以聲明為以下幾種類型:
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
注意被盈,bool和func是簡寫。
這些知識基礎(chǔ)數(shù)據(jù)類型搭伤,還有一些復(fù)雜的只怎,附上鏈接:
https://facebook.github.io/react/docs/typechecking-with-proptypes.html
總結(jié)
props是一個從外部傳進(jìn)組件的參數(shù),主要作為就是從父組件向子組件傳遞數(shù)據(jù)怜俐,它具有可讀性和不變性身堡,只能通過外部組件主動傳入新的props來重新渲染子組件,否則子組件的props以及展現(xiàn)形式不會改變拍鲤。
state
state是什么呢贴谎?
- State is similar to props, but it is private and fully controlled by the component.
一個組件的顯示形態(tài)可以由數(shù)據(jù)狀態(tài)和外部參數(shù)所決定吊洼,外部參數(shù)也就是props罐柳,而數(shù)據(jù)狀態(tài)就是state。
export default class ItemList extends React.Component{
constructor(){
super();
this.state = {
itemList:‘一些數(shù)據(jù)’,
}
}
render(){
return (
{this.state.itemList}
)
}
}
首先甥绿,在組件初始化的時候绞幌,通過this.state給組件設(shè)定一個初始的state,在第一次render的時候就會用這個數(shù)據(jù)來渲染組件莲蜘。
setState
state不同于props的一點是帘营,state是可以被改變的。不過芬迄,不可以直接通過this.state=的方式來修改问顷,而需要通過this.setState()方法來修改state禀梳。
比如,我們經(jīng)常會通過異步操作來獲取數(shù)據(jù)算途,我們需要在didMount階段來執(zhí)行異步操作:
componentDidMount(){
fetch(‘url’)
.then(response => response.json())
.then((data) => {
this.setState({itemList:item});
}
}
當(dāng)數(shù)據(jù)獲取完成后塞耕,通過this.setState來修改數(shù)據(jù)狀態(tài)。當(dāng)我們調(diào)用this.setState方法時嘴瓤,React會更新組件的數(shù)據(jù)狀態(tài)state莉钙,并且重新調(diào)用render方法筛谚,也就是會對組件進(jìn)行重新渲染。
注意:
通過this.state=來初始化state蚊伞,使用this.setState來修改state吮铭,constructor是唯一能夠初始化的地方厚柳。setState接受一個對象或者函數(shù)作為第一個參數(shù)沐兵,只需要傳入需要更新的部分即可,不需要傳入整個對象碳想,比如:
export default class ItemList extends React.Component{
constructor(){
super();
this.state = {
name:‘a(chǎn)xuebin’,
age:25,
}
}
componentDidMount(){
this.setState({age:18})
}
}
在執(zhí)行完setState之后的state應(yīng)該是{name:’axuebin’,age:18}毁靶。
setState還可以接受第二個參數(shù),它是一個函數(shù)预吆,會在setState調(diào)用完成并且組件開始重新渲染時被調(diào)用,可以用來監(jiān)聽渲染是否完成:
this.setState({
name:‘xb’
},()=>console.log(‘setState finished’))
總結(jié)
state的主要作用是用于組件保存岩遗、控制以及修改自己的狀態(tài)凤瘦,它只能在constructor中初始化,它算是組件的私有屬性蔬芥,不可通過外部訪問和修改,只能通過組件內(nèi)部的this.setState來修改笔诵,修改state屬性會導(dǎo)致組件的重新渲染返吻。
區(qū)別
state是組件自己管理數(shù)據(jù),控制自己的狀態(tài)乎婿,可變测僵;
props是外部傳入的數(shù)據(jù)參數(shù),不可變次酌;
沒有state的叫做無狀態(tài)組件恨课,有state的叫做有狀態(tài)組件舆乔;
多用props,少用state希俩。也就是多寫無狀態(tài)組件纲辽。
2.Object.assign方法
參考:https://github.com/ruanyf/es6tutorial/blob/06fac98bb11b0b1f3fccd396d402e918ae3fc002/docs/object.md
基本用法
Object.assign方法用于對象的合并拖吼,將源對象(source)的所有可枚舉屬性,復(fù)制到目標(biāo)對象(target)吊档。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign方法的第一個參數(shù)是目標(biāo)對象怠硼,后面的參數(shù)都是源對象。
注意香璃,如果目標(biāo)對象與源對象有同名屬性葡秒,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性眯牧。
const target =``{ a:``1, b:``1``};
const source1 =``{ b:``2, c:``2``};
const source2 =``{ c:``3``};
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一個參數(shù)炸站,Object.assign會直接返回該參數(shù)。
const obj =``{a:``1};
Object.assign(obj)``=== obj // true
注意點
(1)淺拷貝
Object.assign方法實行的是淺拷貝,而不是深拷貝腿堤。也就是說,如果源對象某個屬性的值是對象忌堂,那么目標(biāo)對象拷貝得到的是這個對象的引用酗洒。
const obj1 =``{a:``{b:``1}};
const obj2 =``Object.assign({}, obj1);
obj1.a.b =``2;
obj2.a.b // 2
上面代碼中,源對象obj1的a屬性的值是一個對象棋嘲,Object.assign拷貝得到的是這個對象的引用。這個對象的任何變化痪伦,都會反映到目標(biāo)對象上面雹锣。
(2)同名屬性的替換
對于這種嵌套的對象,一旦遇到同名屬性辉哥,Object.assign的處理方法是替換攒射,而不是添加匆篓。
-
const target =
{ a:
{ b:
*‘c’, d:*``*‘e’*``*}*``*}*
-
const source =
*{ a:*``*{ b:*``*‘hello’*``*}*``*}*
Object.assign(target, source)
// { a: { b: ‘hello’ } }
上面代碼中,target對象的a屬性被source對象的a屬性整個替換掉了鸦概,而不會得到{ a: { b: ‘hello’, d: ‘e’ } }的結(jié)果窗市。這通常不是開發(fā)者想要的,需要特別小心论熙。
一些函數(shù)庫提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法)摄狱,可以得到深拷貝的合并。
(3)數(shù)組的處理
Object.assign可以用來處理數(shù)組祝谚,但是會把數(shù)組視為對象酣衷。
-
Object.assign([1,
2,
3],
[4,
5])
// [4, 5, 3]
上面代碼中,Object.assign把數(shù)組視為屬性名為 0席爽、1、2 的對象玖像,因此源數(shù)組的 0 號屬性4覆蓋了目標(biāo)數(shù)組的 0 號屬性1炬藤。
(4)取值函數(shù)的處理
Object.assign只能進(jìn)行值的復(fù)制,如果要復(fù)制的值是一個取值函數(shù)上真,那么將求值后再復(fù)制羹膳。
-
const source =
{
-
get foo()
{
return
1
}
};
-
const target =
{};
Object.assign(target, source)
// { foo: 1 }
上面代碼中,source對象的foo屬性是一個取值函數(shù)就珠,Object.assign不會復(fù)制這個取值函數(shù)醒颖,只會拿到值以后,將這個值復(fù)制過去逼侦。
如果該參數(shù)不是對象腰耙,則會先轉(zhuǎn)成對象挺庞,然后返回。
3.ES6拓展運(yùn)算符
參考:http://es6.ruanyifeng.com/#docs/object#對象的拓展運(yùn)算符
擴(kuò)展運(yùn)算符
對象的擴(kuò)展運(yùn)算符(…)用于取出參數(shù)對象的所有可遍歷屬性选侨,拷貝到當(dāng)前對象之中援制。
let z =``{ a:``3, b:``4``};
let n =``{``…z };
n // { a: 3, b: 4 }
這等同于使用Object.assign方法。
let aClone =``{``…a };
// 等同于
let aClone =``Object.assign({}, a);
4.dva connect函數(shù)
參考:
https://dvajs.com/guide/introduce-class.html#connect-%E6%96%B9%E6%B3%95
connect 方法
connect 是一個函數(shù),綁定 State 到 View。
import``{ connect } from ‘dva’;
function mapStateToProps(state)``{
return``{ todos: state.todos };
}
connect(mapStateToProps)(App);
connect 方法返回的也是一個 React 組件掌栅,通常稱為容器組件码泛。因為它是原始 UI 組件的容器,即在外面包了一層 State晌缘。
connect 方法傳入的第一個參數(shù)是 mapStateToProps 函數(shù)痢站,mapStateToProps 函數(shù)會返回一個對象阵难,用于建立 State 到 Props 的映射關(guān)系。
5.dispatch方法
dispatch 方法
dispatch 是一個函數(shù)方法呜叫,用來將 Action 發(fā)送給 State朱庆。
dispatch({
type:``‘click-submit-button’,
payload:``this.form.data
})
dispatch 方法從哪里來?被 connect 的 Component 會自動在 props 中擁有 dispatch 方法傲诵。
前后端傳值示例:
先看一下項目目錄:
1.Index——index.js
import dva from ‘dva’;
import ‘./index.less’;
import ‘./todolist.css’;
import createLoading from ‘dva-loading’
import ‘moment/locale/zh-cn’
import {message, notification} from ‘a(chǎn)ntd’
import roleManage from ‘./application/models/roleManager’
import themeConfig from ‘./application/models/themeManager’
// 1\. Initialize
const app = dva({
onError(e) {
// notification.error({
// description: e.message,
// })
},
});
app.use(createLoading());
// 2\. Plugins
// dbmanager.use({});
// 3\. Model
app.model(require(‘./application/models/userManager’))
….
app.model(require(‘./application/models/todoList’))
// 4\. Router
app.router(require(‘./router’))
// 5\. Start
app.start(‘#root’);
在index.js中定義了model,dva 提供 app.model 這個對象掰吕,所有的應(yīng)用邏輯都定義在它上面颅痊。
2.Model——userManager.js
說直白點,model就是模型菱属,定義了同步或者異步的操作舰罚,通常是前后端傳值的操作。在model中引入service,通過調(diào)用service的具體方法完成post/get請求等前后端傳值赏陵。
userManager.js如下:
import as service from “../services/userManager”;
import alert from ‘../../framework/common/alert’
export default {
namespace: ‘userManager’,
state: {
userList:[],
roleList: [],
spinning: true,
},
reducers: {
save(state, action) {
return {…state, …action.payload};
},
},
effects: {
queryUserList({payload}, {call, put}) {
yield put({type: ‘save’, payload: {spinning: true}})
const {data} = yield call(service.queryUserList)
if (data.code == ‘0’) {
alert(‘查詢用戶’, data)
} else {
yield put({type: ‘save’, payload: {userList: data.payload}})
}
},
addUser({payload}, {call, put}){
const {data} = yield call(service.addUser, payload)
if (data.code == ‘0’) {
alert(‘添加人員’, data)
}
yield put({type: ‘queryUserList’})
},
update({payload}, {call, put}){
const {data} = yield call(service.update, payload)
if (data.code == ‘0’) {
alert(‘修改人員’, data)
}
yield put({type: ‘queryUserList’})
},
deleteUser({payload}, {call, put}){
const {data} = yield call(service.deleteUser, payload)
if (data.code == ‘0’) {
alert(‘修改人員’, data)
}
yield put({type: ‘queryUserList’})
},
queryAllRole({payload}, {call, put}) {
const {data} = yield call(service.queryAllRole)
if (data.code == ‘0’) {
alert(‘查詢角色’, data)
}
yield put({type: ‘save’, payload: {roleList: data.payload}})
},
},
subscriptions: {
setup({dispatch, history}) {
},
},
}
Model 對象的屬性:
namespace: 當(dāng)前 Model 的名稱蝙搔。整個應(yīng)用的 State,由多個小的 Model 的 State 以 namespace 為 key 合成
state: 該 Model 當(dāng)前的狀態(tài)证鸥。數(shù)據(jù)保存在這里勤晚,直接決定了視圖層的輸出
reducers: Action 處理器,處理同步動作鸟蜡,用來算出最新的 State
effects:Action 處理器血淌,處理異步動作
參考:https://dvajs.com/guide/introduce-class.html
3.Service——userManager.js
Service類似Java中的Service,不過這里不用寫接口,直接就是實現(xiàn)類了,在Service中直接調(diào)用方法和后端進(jìn)行數(shù)據(jù)交互癌淮。
import axios from ‘a(chǎn)xios’
export async function queryUserList() {
return axios.get(‘userManager/queryUserList’)
}
export async function addUser(payload) {
return axios.post(‘userManager/addUser’,payload)
}
export async function update(payload) {
return axios.post(‘userManager/update’,payload)
}
export async function deleteUser(payload) {
return axios.post(‘userManager/deleteUser’,payload)
}
export async function queryAllRole() {
return axios.post(‘/role/queryAllRole’)
}
4.Router——router.js
import React from “react”;
import {Route, Router, IndexRoute} from “dva/router”
import UserManager from ‘./application/views/UserManager/index’
import Todolist from‘./application/views/TodoList’
function RouterConfig({history, app}) {
return (
<Router history={history}>
<Route path=“/“ component={NavLayout}>
<IndexRoute component={UserManager}/>
<Route path=“userManager” component={UserManager}/>
//…
<Route path=“todolist” component={Todolist}/>
</Route>
</Router>
)
}
export default RouterConfig
router是路由沦补,router.js文件定義了路徑path和對應(yīng)的跳轉(zhuǎn)頁面,類似Java中的Controller夕膀。如:
Route path=”userManager” component={UserManager}/>
component是組件的意思,即相應(yīng)path需要跳轉(zhuǎn)到的頁面魂奥。userManager組件在開頭已經(jīng)通過import引入了:
import UserManager from ‘./application/views/UserManager/index’
當(dāng)訪問http://localhost:8080/userManager 時會自動找到UserManager組件易猫,(位于application/views/UserManager文件夾下的index.js文件)然后將其渲染成頁面。
PS:到這里其實我們知道接下來要來到這個userManager組件所在的頁面了哈蝇。那之前1.2.3.步中的index和model和service有什么用呢攘已?暫時沒有用,因為這些都是為了后面前后臺數(shù)據(jù)交互(傳值)用的吠勘。
5.View——index.js
View顧名思義是視圖層,視圖層就是頁面,在react中即組件,通常一個頁面組件是頂層組件批旺,也就是一個view诵姜,一個頁面組件中通常會包裹其他各種小組件搏熄。截圖如下:
里面主要包含各種react周期函數(shù)和自定義的各種方法,最后通過render()來渲染出頁面的html。
以上就是頁面的流通過程,下面結(jié)合具體頁面操作來過一遍前后端傳參過程宵凌。
1.進(jìn)入http://localhost:8080/userManager 頁面:
其中import頭部如下:
點擊新增按鈕,對應(yīng)View——index.js中render()方法中如下元素:
<Button onClick={()``=>{this.setState({modalDisplay:``“add”})}}
type=“primary” style={{borderRadius:``15, width:``100}}>``新增``</Button>
react中一切皆組件,這個button也是一個組件,里面包含onClick瞎惫、type译株、style屬性。這里的button并不是自定義的組件,而是是引用自antd的開源組件
PS: antd是螞蟻金服開源的基于react的前端UI組件庫具體可以參考官網(wǎng):https://ant.design/components/icon-cn/)
button常用屬性如下:
2.onClick觸發(fā)箭頭函數(shù)→this.setState({modalDisplay: “add”})
this.setState是react中的語法乘寒,作用是改變當(dāng)前組件的state伞辛。這時夯缺,看一下整個頁面初始化構(gòu)造器中其實是在state中定義了modalDisplay 的:
constructor(props) {
super(props)
this.state = {
userId: window.sessionStorage.getItem(“userId”),
modalDisplay: “none”,
selectUser: null,
modalVisible: false
}
}
通過給modalDisplay賦值,modalDisplay從null變?yōu)榱恕盿dd”,組件的state改變會觸發(fā)相應(yīng)的元素重新渲染,那么對應(yīng)的就是頁面組件中的Modal組件:
<Modal title={selectUser ? “編輯用戶” : “添加用戶”} visible={modalDisplay === “none” ? false : true}
onCancel={() => this.handleCancel() }
cancelText=“關(guān)閉”
okText=“確定”
onOk={() => this.editUser()}>
Modal和button組件一樣竿滨,都是antd組件润文,當(dāng)此組件捕獲到整個state改變后,(modalDisplay從none變?yōu)椤盿dd”),Modal的visible屬性變?yōu)閠rue,故此Modal被渲染了出來,效果是如下彈框:
3.前面只是鋪墊曙砂,只是前端頁面的點擊和渲染骏掀,還沒涉及到前后端數(shù)據(jù)交互柱告,從Modal組件上點擊【確定】時笑陈,正式開始前后端傳參過程涵妥。Modal組件上點擊【確定】調(diào)用組件的editUser()方法。
/編輯用戶/
editUser() {
const {selectUser} = this.state
const {form, dispatch} = this.props
const {validateFields, resetFields} = form
validateFields((errors, values) => {
if (errors) {
return
}
if (!selectUser) {
values.userId = this.state.userId
dispatch({type: ‘userManager/addUser’, payload: values})
resetFields()
this.setState({modalDisplay: “none”})
} else {
values.userId = this.state.userId
dispatch({type: ‘userManager/update’, payload: values})
resetFields()
this.setState({modalDisplay: “none”, selectUser: null})
}
})
}
selectUser是頁面組件初始化constructor中定義的state屬性,初始為null窒所。在editUser()中先判斷selectUser,如果為空則執(zhí)行:
dispatch({type: ‘userManager/update’, payload: values})
不為空則執(zhí)行:
dispatch({type: ‘userManager/addUser’, payload: values})
這里帆锋,dispatch是dva中定義的函數(shù),用來將 Action 發(fā)送給 State。Action 是一個普通 javascript對象皮官,它是改變 State 的唯一途徑实辑。一切觸發(fā)state改變的行為都可以叫Action,此處Action即為:{type: ‘userManager/update’, payload: values}
PS:被 connect 的 Component 會自動在 props 中擁有 dispatch 方法。這里頁面組件在開始已經(jīng)做了connect:
@connect(state =>``Object.assign({}, state.userManager,``{loading: state.loading.models.userManager}))
這個connect看起來挺復(fù)雜的讯沈,用到了箭頭函數(shù)婿奔、Object.assign()方法。但是其原理很簡單挤茄,就是綁定state到view冰木。此處即綁定當(dāng)前組件的state到models下的userManager.js(view)
4.此處以dispatch({type: ‘userManager/addUser’, payload: values})
為例看一下添加用戶的前后端交互流程。
state改變后觸發(fā)contact綁定過的models中userManager.js中addUser方法:
effects: {
addUser({payload}, {call, put}){
const {data} = yield call(service.addUser, payload)
if (data.code == ‘0’) {
alert(‘添加人員’, data)
}
yield put({type: ‘queryUserList’})
},
…
}
在這里call和put方法都是effect內(nèi)部常用的處理函數(shù)歇终。
call:執(zhí)行異步函數(shù),這里通過call方法調(diào)用service來實現(xiàn)向后臺傳值,
put:發(fā)出一個 Action逼龟,類似于 dispatch,這里通過put一個Action:{type: ‘queryUserList’}來改變前臺state狀態(tài)。
5.service.addUse
這里就是前臺頁面向后端傳值的最后一個步驟.