typescript + react 專案開發體驗之 react狀態管理

leekkj發表於2019-01-19

目錄

Context

業務當中,常常存在元件之間資料共用的問題,因為元件層級關係可能很複雜,通過props進行傳遞將會極其的繁瑣。react為了解決這個問題,提供了一個context方便實現資料共享,使用方式也極其的簡便。

const {Provider, Consumer} = React.createContext('nihao');

const demo = value=>{
    return <div>{value}</div>
}
// 方式1
<Provider value="hello world">
    // ... 若干層級或者交叉,這裡展示 hello world,記住這裡按照上一章說的props render的寫法
	<Consumer>
    	{demo}
    </Consumer>
</Provider>
// 方式2
// ...父層級,最頂層沒有使用Provider會使用預設值展示 'nihao'
<Consumer>
   {demo}
</Consumer>
// 為了使得資料能獲得相應並且使得檢視重新整理我們們可以
const {Provider, Consumer} = React.createContext();
class Pvd extends React.Component {
  	constructor(props) {
        super(props);
        this.state = {
          hello: 'hello',
        };
    }
  	this.setHello = hello => this.setState({ hello });
    render(){
		return <Provider value={{hello: this.state.hello, setHello: this.setHello}}>
            // ... 若干層級或者交叉,這裡展示 hello world,記住這裡按照上一章說的props render的寫法
            {this.props.children}
        </Provider>
    }
}
const App = (props)=>{
    const ConsumerU = props.context.Consumer || Consumer;
    return <Pvd>
       <ConsumerU>
        {
        	props=>{
        		return <div onClick={()=>props.setHello(Math.random())}>{props.data}</div>
    		}
    	} 
       </ConsumerU>
    </Pvd>
}
複製程式碼

redux

實際spa專案中,公用資料模型往往比較複雜,使用上面那種方式很容易邏輯混亂,為了使其能夠便於管理,常常會引入redux。通過閱讀redux原始碼發現,他是典型的以訂閱釋出模式設計思路,用純函式處理資料流。主要有以下一些概念:

  1. reducer 用作定義 state 更新方式。
  2. action 用作 state 更新指令,一般 type 是它必有的屬性。
  3. reducer 接收 action 從而選取 state 更新方式進行更新,注意reducer必須有一個當存在不能識別action時候的處理方式。
  4. applymiddleware 用於一些 action 的前置處理。

使用思路:

  1. 通過createStore建立一個資料倉儲store。
  2. store 能 dispatch 一個 action ,reducer 接收到 action 會決定接下來 state 的值。
  3. store 能夠 store.subscribe() 增加訂閱者,該方法會返回一個用於解除訂閱的方法。當新增訂閱後會通過一個佇列對他們進行維護,當發生 dispatch 行為時,佇列的監聽回撥會被依次呼叫。
  4. 當 dispatch 發生後 subscribe 的回撥會執行,此時可以通過 store.getState() 根據 state 的值做出相應的反應。
  5. 當你某些 action 需要前置處理時你需要使用 middleware。applymiddleware的設計思想

redux-saga

作為redux處理非同步資料流的中介軟體之一,saga 利用 generator 可以多次返回以及內部掛起機制的特性(具體就不在這展開了),更好的處理非同步資料流,下面用一個例項來展示他們的用法。ps:為什麼不用thunk,因為簡單的令人髮指,逼格完全不夠因為thunk的設計思想是dispatch非標準格式的 action,而且實際資料互動還是放在業務邏輯(這個是重點)。

import {combineReducers, createStore, applyMiddleware, Dispatch, Middleware, AnyAction, MiddlewareAPI} from 'redux';
import createSagaMiddleware from 'redux-saga';
import root from './saga';
// 兩個資料模型
import user from './model/User';
// 頁面控制模型
import pageCommonConfig from './model/PageCommonConfig';

// log中介軟體
const logMiddleWare:Middleware = (store: MiddlewareAPI)=>(next: Dispatch<AnyAction>)=>(action:any)=>{
    console.log('log:', action);
    return next(action);
}
// 呼叫 redux-saga 包中的 createSagaMiddleware ,建立一個sageMiddleware
const sageMiddleware = createSagaMiddleware();
export default createStore(
    combineReducers({ user , pageCommonConfig}),
    applyMiddleware(
        logMiddleWare,
        sageMiddleware
    )
);
sageMiddleware.run(root);


// root 定義
import { all, fork, CallEffectFn } from 'redux-saga/effects';
import * as User from './User';
// import * as Goods from './Goods';

var forkArray:any[] = [];
// fork 是非阻塞試監聽的陣列,因為我們需要同時監聽多個action
const makeItertorArrayFrok = (arr: CallEffectFn<any>[])=>arr.map(item=>fork(item));

const geratorForkArray = (...arg: {}[])=>{
    arg.forEach(item=>{
        forkArray = forkArray.concat(makeItertorArrayFrok(Object.values(item)));
    })
}
// 把所有要監聽的 generator 物件都包裝到一個陣列
geratorForkArray(User);

export default function* root(){
    try {
       // 通過 all 啟動對所有 generator 的監聽。
       yield all(forkArray);
    } catch(e){
        console.log(e);
    }
}

// User Address 的 CRUD
import { setUserCredit, setUserAddress, setAddressCodeTable, addOrEditUserAddress } from '@store/model/User/actions';
import { getUserCredit, getUserAddress , deleteUserAddress, modiUserAddress, getAddressTableCode} from '@api/user';
import { modiAddressItem as modiAddressI } from './actions';
import { take, call, put, takeEvery, select} from "redux-saga/effects";

// 刪除警告
function showDeleteAlert(title= '警告', tips= '你確定要刪除當前地址嗎?'){
    return new Promise((resolve,reject)=>{
        const alertInstance = alert(title, tips, [
            { text: '取消', onPress: () => {reject();alertInstance.close()}, style: 'default' },
            { text: '確定', onPress: () => {resolve();console.log('確定')} },
        ]);
    })
    
};
// take 用於監聽 action
// call 用於呼叫函式,該函式應該是個 generator 函式或者返回一個Promise。
export function* queryAddress(){
    try{
        // take 只監聽 type 為 queryAddress 的 action 一次。
        // 因為查詢地址只用呼叫一次,進行基礎的資料構建即可。
        yield take('queryAddress');
        // 當有這個 action 被派發時往下走去請求資料
        const res = yield call(getUserAddress);
        // 拿到結果往 reducer 層拋
        yield put(setUserAddress(res));
    } catch (e) {
        
    }
}

export function* deleteAddressItem(){
    // takeEvery 監聽多次,接收一個 generator 函式作為引數
    yield takeEvery('deleteAddressItem',function*(action: AnyAction){
        try{
            // 刪除警告,等待響應
            yield call(showDeleteAlert)
            // select 用於查詢 state 裡的值,接收一個方法。然後把值拷貝出來,因為是引用型別,不要改變原有結構。
            const address: Address[] = [...yield select((state:MallStoreState)=>state.user.address)];
            // 找
            let result = address.findIndex(val=>val.addressId === action.id);
            // 找到
            if(result !== -1){
                // 預設地址不讓刪
                if(address[result].isDefaultAddress == "Y")return Toast.fail('預設地址不能刪除', 1);		// 伺服器刪除
                yield call(deleteUserAddress, address[result].addressId, address[result].channel);
            }
            // 本地刪除
            if(address){
                address.splice(result,1);
                yield put(setUserAddress(address));
                Toast.success('刪除成功', 1);
            }
        } catch (e) {
        
        }
    });
    
}

// 修改預設地址
export function* modiAddressItemDefault(){
    // 不解釋
    yield takeEvery('modiAddressItemDefault',function*(action: AnyAction){
        try{
            // 不解釋+1
            const address: Address = yield select((state:MallStoreState)=>(state.user.address as Address[]).find(val=>val.addressId === action.id));
            
            if(address.isDefaultAddress === "N") {
                // 它還可以拋給自己就是往下拋不拋到reducer層
                yield put(modiAddressI(Object.assign({}, address, {isDefaultAddress : 'Y'})));
            }
        } catch (e) {
            console.log(e);
        }
    })
}
// 不解釋+2
export function* modiAddressItem(){
    
    yield takeEvery('modiAddressItem',function*(action: AnyAction){
        try{
            
            const addressId = yield call(modiUserAddress, action.address);

            yield put(addOrEditUserAddress(Object.assign(action.address, {addressId: +addressId})));
        } catch (e) {
        
        }
    });
    
}

複製程式碼

react-redux

react 元件公共資料管理講完了,redux 講完了。要將 reduxreact 串聯,我們一般會用到 react-redux 。根據我們常過的 `react-redux 方法和元件,我們嘗試著理解一下他們的構造思路。

  1. Provider
  • 首先這是一個 react 元件,元件引數接收一個 reduxstore
  • context.Provider 中的 ,接收的肯定是 store.getState 的值。
  • 為了 state 的調整能作用於元件,元件內部肯定會有一系列的針對 storesubscribe
  • 當元件銷燬的時候需要登出相應的 subscribe
  1. connect
  • 接收mapStateToProps 方法,接收一個 state 作為引數,返回一個 state 的一個對映集合
  • 接收mapDispatchToProps 方法,接收一個 dispath 作為引數,返回一個dispatch相應action的集合
  • 返回一個接收Component的函式,將上面兩個方法生產的結果作為props傳遞給Componet。最終返回一個高階元件,可能比較抽象,這裡大概寫一下。
// 為了讓 mapStateToProps mapDispatchToProps  能拿到 state 和 dispatch我們需要藉助一個Consumer
const connect = (mapStateToProps, mapDispatchToProps)=>{
    return WarpComponent=>{
        // 內部閉包
        return props=>{
            const Consumer = props.context.Consumer;
            return <Consumer>
                {
                    store=>{
                        return <WarpComponent
                				...props
				                ...mapDispatchToProps(store.dispatch)
                                ...mapStateToProps(store.state)/>
                        }
                    }
                }
            </Consumer>
        }
		
    }
} 
複製程式碼

實際原始碼思路和這個也是差不多的,只不過多了很多資料的normalize處理。這裡就不展開詳述了。

that's all tk.

相關文章