開頭先寫一句理論:所謂狀態機,是一種抽象的數據模型,是“事物發展的趨勢”,其原理是事件驅動。廣泛地講,世界萬物都是狀態機。 一、狀態機是一種抽象的數據模型 在react中,props和state都可以用來傳遞數據。這裡作一下區分。 1.props props用於組件間的數據傳遞。其本身只是一個屬性, ...
所謂狀態機,是一種抽象的數據模型,是“事物發展的趨勢”,其原理是事件驅動。廣泛地講,世界萬物都是狀態機。
一、狀態機是一種抽象的數據模型
在react中,props和state都可以用來傳遞數據。這裡作一下區分。
1.props
props用於組件間的數據傳遞。其本身只是一個屬性,不是一個狀態機。
從子組件的角度看,子組件無法擅自修改父組件通過屬性傳遞的數據,因此具有單向數據流的特點。
2.state
state用於設置組件本身的狀態。state用於用戶數據交互、事件監聽。
setState會調用render()方法以重新渲染。因此,setState不能寫到render()裡面。
當state數據發生改變時,該組件和state數據作用域內的子組件都會一層一層地重新渲染。
3.state與props
props傳遞state中的數據時,如果數據發生改變,子組件會被重新渲染。
子組件可以通過調用父組件的方法來修改父組件傳遞過來的數據。
import React from 'react'
import ReacDOM from 'react-dom'
import { Button } from 'antd-mobile'
class SubCom extends React.Component{
constructor(props){
super(props);
this.state={ name: "Jan" }
}
render(){
// 將子組件數據傳遞給父組件
console.log("我被重新渲染了");
return <Button type='primary' onClick={()=>this.props.handleChange("name", this.state.name)}>點我</Button>
}
}
// 通過函數改變state狀態修改父組件傳遞的值
class App extends React.Component{
constructor(props){
super(props);
this.state = {
string: "我是一個button"
};
this.handleChange = this.handleChange.bind(this)
}
handleChange(key, val){
this.setState({
string: "我是一個藍色的button",
key: val
})
};
render(){
console.log(this.state);
return <SubCom handleChange={ this.handleChange }/>
}
}
ReacDOM.render(
<App />,
document.getElementById("root")
);
4、state的局限性
state作為單個組件的狀態機,關註的只是單個組件內部。如果一個子組件需要修改一個父組件的state,那麼父組件就需要將handleChange一級一級地傳遞給這個組件,並且要保證整個過程不會被其它狀態或屬性干擾。並且當父組件的state發生改變時,其到這個自組件的所有中間組件都要重新渲染,這顯然不符合我們的需要。
因此,在複雜的數據交互中,state就顯得力不從心。這時,一種更為抽象的數據模型應運而生,那就是redux。
5、redux插件
redux、redux-thunk、react-redux一起解決了上述問題。
redux-thunk、react-redux主要工作是建立非同步狀態機,並能夠只重新渲染state狀態涉及的子組件,而其它無關中間組件則不會重新渲染。
redux代表著更為抽象的數據模型,它的主要內容有兩個:一是打破組件內部this.state的孤立性,使得各層級的組件能夠共用一個state;二是解耦,將一些公用的狀態抽離成一個狀態樹,專門處理特定的數據。
6、redux狀態機與props、state的關係
redux狀態機是抽離的公用的state。
和組件內的state一樣,需要用props來傳遞,這種傳遞只有一層:整個app的最外層provider,以及被connect裝飾的子組件。
可以從子組件的props中獲取redux狀態機中的state數據。
二、使用實例
一個用戶註冊、登錄和修改個人信息的狀態機。
// src/reducer.js
import axios from 'axios';
import {getRedirectPath} from '../utils/userRedirect'
const ERROR_MSG = 'ERROR_MSG';
const LOAD_COOKIE = "LOAD_COOKIE";
const AUTH_SUCCESS = "AUTH_SUCCESS";
const CLEAR_COOKIE = "CLEAR_COOKIE";
// 獲取用戶登錄信息
const initState = {
msg: '',
user:'',
type:'',
redirectTo:''
};
export function user(state=initState, action) {
switch (action.type){
case ERROR_MSG:
return {...state, isAuth: false, msg: action.msg};
case LOAD_COOKIE:
return {...state, ...action.payload};
case AUTH_SUCCESS:
return {...state, ...action.payload, redirectTo: getRedirectPath(action.payload)}; // getRedirectPath是根據返回data中的用戶類型返回相應url的處理函數
case CLEAR_COOKIE:
return {...initState, redirectTo:'/login'}; // 將登錄信息清空,回到初始狀態,並重定向到login
default:
return state;
}
}
// 假如註冊、登錄和更新數據的action函數以及返回的狀態都一樣,可以把它們合併到一起。
function authSuccess(data){
return {type: AUTH_SUCCESS, payload:data}
}
function errMsg(msg) {
return {type: ERROR_MSG, msg}
}
// 註冊時獲取用戶信息
export function register({user, pwd, repeatPwd, type}) {
if(!user || !pwd || !type){
return errMsg("用戶名和密碼不能為空")
}
if(pwd !== repeatPwd){
return errMsg('密碼和確認密碼不一致')
}
return dispatch=>{
axios.post('/user/register', {user, pwd, type}).then(res=>{
if(res.status===200 && res.data.code===0){
dispatch(authSuccess(res.data.data))
}else {
dispatch(errMsg(res.data.msg))
}
})
}
}// 登錄時獲取用戶信息
export function login({user, pwd}) {
if(!user || !pwd){
return errMsg("用戶名密碼必須輸入")
}
return dispatch=>{
axios.post('/user/login', {user, pwd}).then(res=>{
if(res.status===200 && res.data.code===0){
// console.log(res.data.data);
dispatch(authSuccess(res.data.data)) // 將loginSuccess改成authSuccess
}else {
dispatch(errMsg(res.data.msg))
}
})
}
}
// 更新數據
export function update(data) {
return dispatch=>{
axios.post('user/update', data).then(res=>{
if(res.status===200 && res.data.code===0){
dispatch(authSuccess(res.data.data))
}else {
dispatch(errMsg(res.data.msg))
}
})
}
}
// 讀取cookie
export function loadCookie(data) {
return {type: LOAD_COOKIE, payload: data}
}
// 清除cookie
export function clearCookie() {
return { type: CLEAR_COOKIE }
}
狀態機的使用例子。這裡沒有server端。
import React from 'react'
import ReacDOM from 'react-dom'
import {createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider, connect } from 'react-redux'
import {List, InputItem, WingBlank, WhiteSpace, Button, NavBar } from 'antd-mobile'
import { login } from "./reducer";
const store = createStore(user, compose(
applyMiddleware(thunk),
window.devToolsExtension?window.devToolsExtension():f=>f));
@connect(state=>state, { login })
class App extends React.Component{
constructor(props){
super(props);
this.state={
user: '',
pwd: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleLogin = this.handleLogin.bind(this);
}
handleChange(key, val){
this.setState({[key]: val})
}
handleLogin(){
this.props.login(this.state)
}
render(){
return (
<div>
<WingBlank>
<NavBar mode="dark">登錄頁面</NavBar>
<List>
<WhiteSpace />
<InputItem onChange={v=>this.handleChange('user', v)}>用戶</InputItem>
<WhiteSpace />
<InputItem onChange={v=>this.handleChange('pwd', v)} type='passwd'>密碼</InputItem> <WhiteSpace />
<Button type='primary' onClick={this.handleLogin}>登錄</Button>
</List>
</WingBlank>
</div>
)
}
}
ReacDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById("root")
);
三、redux簡單實現
1、redux簡單用例
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
function reducer(state=0, action) {
// action都是事件類型
switch(action.type){
case 'addBBQ':
return state + 1;
case 'reduceBBQ':
return state - 1;
default: return 10
}
}
// 1.新建atore
const store = createStore(reducer);
// 2.派發事件 傳遞action
store.dispatch({type: "addBBQ"});
store.dispatch({type: "reduceBBQ"});
// 3.訂閱消息
function listener() {
const current = store.getState(); // 獲取狀態
console.log(`現在的BBQ數量是${current}。`);
}
// 4.監聽變更
store.subscribe(listener);
// 5.監聽派發事件
store.dispatch({type: "reduceBBQ"});
store.dispatch({type: "reduceBBQ"});
store.dispatch({type: "reduceBBQ"});
ReactDOM.render(
<p> demo </p>,
document.getElementById('root')
);
2、redux簡單實現
import React from 'react'
import ReactDOM from 'react-dom'
function reducer(state=0, action) {
switch(action.type){
case 'addBBQ':
return state + 1;
case 'reduceBBQ':
return state - 1;
default: return 10
}
}
// 寫一個簡單的redux
function createStore(reducer) {
let currentState = {};
let currentListeners = [];
function getState() {
return currentState;
}
function subscribe(listener) {
currentListeners.push(listener);
}
function dispatch(action) {
currentState = reducer(currentState, action);
currentListeners.forEach(v=>v())
}
dispatch({type: '@#$%^&*('}); // 執行一遍獲取預設state
return { getState, subscribe, dispatch }
}
// 1.新建atore
const store = createStore(reducer);
// 2.派發事件 傳遞action
store.dispatch({type: "addBBQ"});
store.dispatch({type: "reduceBBQ"});
// 3.訂閱消息
function listener() {
const current = store.getState(); // 獲取狀態
console.log(`現在的BBQ數量是${current}。`);
}
// 4.監聽變更
store.subscribe(listener);
// 5.監聽派發事件
store.dispatch({type: "reduceBBQ"});
store.dispatch({type: "reduceBBQ"});
store.dispatch({type: "reduceBBQ"});
ReactDOM.render(
<p> demo </p>,
document.getElementById('root')
);
兩者的結果完全一致。