標籤:

react redux 某個state變化之後 如何觸發一些操作?

組件A和組件B有聯動關係,當組件A中dispatch一個action之後,state有變化,這時組件B正對這個狀態變化需要有一些邏輯上的處理,最後再更新自己的state。

目前這麼一個簡單的功能,在redux中沒有找到解決方案。

A =》state change =》觸發B的business logic =》B的狀態更新

如果我不用redux,由我自己寫的話,這將是一個典型的publish subscribe。


組件不應該有業務邏輯,這個邏輯應該放在處理組件 A dispatch 出來的 action 的 reducer 里。

action 並不是引起 state 變化,引起 state 變化的是 reducer。reducer 引起多個 state 變化是沒問題的,並不是說 A dispatch 的 action,處理它的 reducer 就不能更改 B 組件使用的 state。

redux 的 dispatch / reduce,就是和 publish / subscribe 一樣的模型,只不過用詞不同而已。dispatch 一個 action,就是 publish 一個 event。reducer 檢查 action 的 type,就是 subscribe 不同類型的 event。

如果你用 mobx 而不是 redux,就可以用 computed state 實現這種需求:(@)computed | MobX。

mobx 好。

如果用 redux,就沒有 state 和 props 的區分了。組件都應該用 props,不應該有任何組件私有的 state。上面兩段話里的 state,實際是 props。因為棄用 react 很久,我都忘記 react 有 state 和 props 的區分了,都說成 state 了。不過解決辦法是沒錯的。用了 redux,組件狀態都用 props 傳入,沒有組件內部狀態。所有狀態都在一個全局的 store 里。


作為redux重度使用者,說下我的建議。

1、先說redux的作用

代理top-level single data flow,從最外層的container下發數據流觸發dumb components的更新。

2、題主的業務場景是否可以由redux解決

答案肯定是可以的,但不一定合理。

下發props告知組件B執行更新,用過react的同學都很熟悉這一套,這邊不贅述。

3、業務場景的特殊性

組件交互存在兩種情形,一種是純數據模型層面的組件映射關係,完全可以由redux處理;另一種是eventEmitter層面的交互,比如使用ng2的eventEmitter或者vue的$dispatch場景。

對於後者redux其實是提供解決方案的,一種是寫個小型中間件,專門用於處理pub/sub,另一種是利用redux的subscribe介面添加觀察者函數來觸發pub/sub。

4、是否真的需要eventEmitter

不需要。

既然你用的是react + redux,就應該遵循它的設計原則,對組件劃分的時候,建立足夠好的view model一一映射關係。沒有什麼是redux解決不了的,如果有,那就重新思考下組件設計是否合理,如果還不行,那就考慮寫個中間件。

以上,一點小建議


謝叔叔 @徐飛 邀

首先,你所稱的組件是怎麼定義的,在你的例子中我理解的組件是獨立於系統,可複製到任何 React 構建的系統中的,也就是說它只具備最基本的輸入和輸出,類似於 function。

那麼在 redux 系統中,A 組件 change state 後應該會拋出一個回調給到父級由父級去 dispatch action,至於 A 組件內是怎麼實現是一個黑盒,可以是 nested redux store,也可以只是利用 state。那麼 B 組件也是同樣,只關心自己的輸入和輸出 ,由外部 change 改變 B 組件 狀態並更新。

回到你的理解,組件其實也是 React 組件,只不過用到了應用全局的 redux store,那麼它就只能在應用中的使用。在這個邏輯下,A 組件 dispatch action 後改變了 store 中 B 組件需要的狀態,B 組件可以從父級的 connect 拿到所需要的 state,也可以在 action creator 中拿到。只不過 這裡所謂的 A B 組件都是在該系統中存在,跳出應用沒有意義。


大概明白了答主的意思。

如果A,B存在state上的共用的話,那這個state本身可以有兩種情況:

1.它是A,B的共有state,應該被提取出來

2.它不是B的state,而是B的props

可以針對這兩種情況來進行處理


很典型的selector用例。

舉個例子,比方說我們有一個userReducer是長這樣的

{
userList: [
id: &,
name: &,
registered: &
...
]
}

假設 Component A dispatch一個action修改userList, 而Component B 需要用到filtered userList(所有registered為true的user)。

我們就可以寫這樣一個selector:

export const selectRegisteredUserList = (state) =&>
state.userReducer.userList.filter((user) =&> !!user.registered);

然後在Component B中的mapStateToProps函數使用selector

export default connect((state) =&> ({
registeredUserList: selectRegisteredUserList(state)
}))(ComponentB);

當然也有可能Component B 不是 Connected Component,那就在Connected Component中使用Selector然後把registeredUserList傳到單程props Component B即可。

擴展1:

這樣有一個問題就是對於比較複雜的計算,如果Connected Component需要監聽很多reducer的話,將在每次store變化的時候就要執行一遍selector。reactjs/reselect: Selector library for Reduxreactjs/reselect: Selector library for Redux 可以解決這個問題,原理就是memoization,給定同樣的input,selector不進行計算,而是把cached output返回。

這樣selector的代碼就變成:

import { createSelector } from "reselect";

export const selectRegisteredUserList = createSelector(
(state) =&> state.userReducer.userList,
(userList) =&> userList.filter((user) =&> !!user.registered)
);

如果再使用ramda,就可以寫point free style:

import { createSelector } from "reselect";
import { path, filter, prop } from "ramda";

export const selectRegisteredUserList = createSelector(
path(["userReducer", "userList"]),
filter(pipe(prop("registered"), Boolean))
);

擴展2:

selector不需要和reducer一對一綁定,selector的input是整個state,這就可以得到很大的靈活性。比方說,Component C 需要用到currentUser,和currentUser對應的tweet,我們就需要這樣的root reducer structure:

{
userReducer: {
userList: [
id: &,
name: &,
registered: &
...
],
currentUserId
},
tweetReducer: {
tweetList: [
id: &,
userId: &,
tweet: &,
...
]
}
}

然後我們就需要一個selectCurrentUserWithTweet函數了

export const selectCurrentUser = createSelector(
(state) =&> state.userReducer,
({ userList, currentUserId }) =&>
userList.find((user) =&> user.id === currentUserId)
);

export const selectCurrentUserWithTweet = createSelector(
(state) =&> selectCurrentUser(state),
(state) =&> state.tweetReducer.tweetList,
(currentUser, tweetList) =&> {
const tweetListForUser = tweetList.filter((tweet) =&>
tweet.userId === currentUser.id
);
return {
id: user.id,
name: user.name,
tweets: tweetListForUser
};
}
);

這樣,Component C就可以有一個currentUserWithTweet prop傳下來,可以直接顯示該用戶的tweet信息,而不需要其他用戶的信息傳下來。


「組件A中dispatch一個action之後,state有變化」、「最後再更新自己的state」,這裡的 state 是指 react state,還是 redux store state?有些模稜兩可,那麼既然應用了 redux,state 應該統一交給 redux 來管理,組件 A 與組件 B 的狀態應該均依賴於 redux store state,但我覺得此非問題的本質,本質在於如何處理 數據/狀態 間的依賴

redux 強調單一數據源,將原本分散在各個 react component 的 state 做了統一管理,也就是 store。store state 全局可讀,component 通過 react-redux 的 connect 獲取,action 通過 middleware 的 getState 獲取。在應用場景中,我們一般對於 store state 都會進行劃分,最後通過 combineReducers 將每個 reducer 更新的分片 state 組合在一起,相應的 reducer 只能更新自己劃分到的 state,因此 store state 全局可讀,分片可寫

回到題主的場景中,可能會出現以下幾種情況:

情況1:組件 A 與組件 B 狀態依賴同一分片 store state

這種情況下,組件 A dispatch 一個 action,應該在這個 action 中完成組件 B 的邏輯判斷,最後將組件 A、B 的狀態在這個 action 的 reducer 一起更新。@唐生 的回答中將組件 B 的業務邏輯放入 reducer,我認為可能會不合理, reducer 需是純函數,我並不確定這段業務邏輯是否可能產生副作用。

const action = (A_State) =&> {
// B 根據 A_State 完成邏輯判斷
const B_State = Logic(A_State);
return { type: CHANGE_A_B_STATE, payload: {
A_State,
B_State,
}};
}

function reducer(state = initialState, action) {
switch (action.type) {
...
case CHANGE_A_B_STATE: {
const { A_State, B_State } = action.payload;
return {
...state,
A_State,
B_State,
};
}
...
}
}

情況2:組件 A 與組件 B 狀態不依賴同一分片 store state

這裡有兩種做法,一種是組件 B 利用 connect 在 component 中獲取組件 A 的狀態,componentWillReceiveProps 監聽組件 A 的狀態變化。

@connect((state) =&> {
// 處於不同的 state 分片中
return {
...state.B,
A_State: state.A.A_State,
}
}, actions)
class B extends Component {
...
componentWillReceiveProps(nextProps) {
if (!_.isEqual(this.props.A_State, nextProps.A_State) {
// 觸發 B 的邏輯判斷
nextProps.props.Logic(A_State);
}
}
...
}

另一種與情況 1 差不多,組件 A 與組件 B 的 reducer 同時監聽相同的 action,分別更新自己管理的 state。

const A_action = (A_State) =&> {
// B 根據 A_State 完成邏輯判斷
const B_State = Logic(A_State);
return { type: CHANGE_A_B_STATE, payload: {
A_State,
B_State,
}};
}

function A_reducer(state = AInitialState, action) {
switch (action.type) {
...
// action.type 相同
case CHANGE_A_B_STATE: {
const { A_State } = action.payload;
return {
...state,
A_State,
};
}
...
}
}

function B_reducer(state = BInitialState, action) {
switch (action.type) {
...
// action.type 相同
case CHANGE_A_B_STATE: {
const { B_State } = action.payload;
return {
...state,
B_State,
};
}
...
}
}

對於這樣的 "變更響應" 場景,基於 Reactive Programming 的 mobx 可能會更自然一些,來看看 mobx 的實現。如果組件 A 與組件 B 所依賴的是同一個 Model,那麼在 Model 中監聽組件 A 改變的狀態,當其發生變化時,B隨之進行邏輯處理。

class Model {
@observable A_state;
@observable B_state;

@action change_A_State = (A_state) =&> {
this.A_state = A_state;
}

Logic = (A_State) =&> {
...
return B_State;
}

reaction_A_State = reaction(() =&> {
return this.A_state;
}, (A_state) =&> {
this.B_State = this.Logic(A_State);
})
}

但如果組件 A 與組件 B 由於業務原因,分屬於不同的 Model ,但產生了聯動關係,那麼又該怎麼辦呢?這也是我在看 mobx 時想到的一個問題,如何處理跨 Model 的依賴。我目前的辦法是將 Model 的實例與 Model 實例組合一下,也就是加一個 Model 控制當前多個 Model 的實例。

class A_Model {
@observable A_state;

@action change_A_State = (A_state) =&> {
this.A_state = A_state;
}
}

class B_Model {
@observable B_state;

Logic = (A_State) =&> {
...
return B_State;
}

@action change_B_State = (A_State) =&> {
this.B_State = Logic(A_State);
}
}

class Union_Model {
@observable A_state;

constructor(A_Obj, B_Obj) {
this.A_state = A_Obj.A_state;
}

reaction_A_State = reaction(() =&> {
return this.A_state;
}, (A_state) =&> {
B_Obj.change_B_State(A_state);
})
}

const unionModel = new Union_Model(new A_Model(), new B_Model());

以上是我個人的一些拙見,歡迎大大們批評指正。


將b依賴的state作為props傳入,然後在componentwillupdate監控依賴的state是否有變化然後執行相應動作。一般來說這種需求與redux關係不大,主要還是利用好react組件的生命周期。或者就像你說的用eventemitter之類的庫也是可以解決的。


寫一個middleware就可以實現這個功能。

因為在Redux中要修改state必須要通過dispatch action,而middleware就是處理action用的,那就在這個定製middleware里在調用next之前保存一個對應state的值,調用next之後比對一下state,如果有變化立刻就發現了。


很簡單啊,你寫個middleware。 middleware 提供一個 listener 管理器。可以 subscribe,並監聽對應數據,執行相應callback 。但是為什麼沒有人這麼搞呢,因為我們更推崇監聽action 而不是監聽state 變化,這樣邏輯更加清晰。而監聽action 社區流行的輪子就多了。比如最火的 redux-saga ,redux-observable


Redux-saga 題主可以看哈,同時還有其他的庫你也可以看看!


首先假設AB組件在一個容器組件中,因為你沒提哈,你應該說一下關聯組件的層級關係。就相當於AB組件都是通過 props 進行更新。然後就簡單了,你要 A 組件發起一個 action,先改變狀態,然後在 B 組件重新渲染前對這個狀態進行重新處理,首先這個重新處理有問題,在 redux 的狀態變化只發生在 reduce 函數中。所以渲染 B 前對 state 進行再處理寫到 reduce。

然後你這裡沒有說清楚你的場景。

1. 為什麼要用渲染 B 前再處理 state ,一個 action 只處理一個動作,或者邏輯並行或者嵌套的多個動作。

2. 是否是該發起多個 action ,比如 A 發起第一個 action ,成就了一次渲染,然後再這個action 完成後,再發起對 B 組件的 state 修改的 action ,然後狀態變化的邏輯寫到 reduce ,這是第二次影響 B 的行為。

總之我覺得你的場景說的不是太清楚,還不如把代碼放到在線的去讓大家看哈。


如果不用Redux,單純用React的話,也不推薦使用Event 或者Pub/Sub模式。官方文檔有推薦方案,那就是Lifting state up。將state提升到兩個組件的共同父組件里。將state及updater以props形式傳入A,B組件。如果組件需要在狀態變化的時候做一些什麼事情的話,可以通過在componentWillRecieveProps或者componentWillUpdate里判斷props和nextProps的不同實現。

如果用Redux,則是將該狀態提升出來放入redux中,同時connect給兩個組件,其他相同。


沒想到會有這麼多人回答,謝謝大家。我現在覺得寫個中間件,或者直接使用redux-saga redux-observable應該是正解


這個要看A,B組件的關係吧,如果是簡單的父子組件,那props就可以搞定;如果A,B層級過深或者同級組件,這個時候就用redux或者自己寫一個事件發布訂閱。


將B組件用redux的connect裝飾一下成為Container組件,將A組件dispatch action改變的state(redux)注入到B組件,這樣就可以在B組件的componentWillReceiveProps方法中獲取到該state(redux)的變化,這時候B組件再setState改變自己的state(B組件內的)。


react redux 中有一個 state 樹,具體可看看知乎專欄


A 和 B 通過 connect 綁定同一個全局 state ,

當 A 通過 action 觸發此 state 變更後,

B 的 componentWillReciveProps 觸發,

此時再通過 setState 修改 B 自身 state

不知能否滿足題主需求


咦~組件B接受組件A那個會變化的state作為props不就好了(難道我沒get到點?╮(‵▽′)╭)


沒看懂樓主說的state是react state還是redux中的state。

一、不使用redux等框架:

這種情況簡單看來就是A、B組件有耦合關係,即一方生產數據,另一方消費數據,在純react中沒有太好的辦法解決平級組件之間的數據監控問題,一般利用上一級的組件做數據路由。

假設一個頁面分為:

Page &<- modules &<- components

要做到A、B組件對數據的監控,可以強行把A、B組件的state,變成module中的state,通過props傳給A、B組件。

這種模式並不優雅,會把module層變得非常臃腫和複雜,並且耦合關係非常緊密。

二、使用redux等框架:

1、redux:可以滿足「生產」、「消費」的需求,並且redux支持訂閱者模式。

2、redux + redux-saga(或mobx):在滿足「1」的情況下,業務邏輯放到redux-saga中做掉,view層(即react)相對來做更加單純(data -&> view)。


B 不應該有局部 state 依賴於 redux 的 state,也不應該有業務邏輯。

將 B 的 state 改為 props,由 redux 的 state 經過 selector 傳遞進來。


推薦閱讀:

redux到mobx到dob……前端發展好快?
感覺redux寫起來很麻煩,目前有那些其他的狀態管理方案?
redux 中的 state 樹太大會不會有性能問題?
redux中所有state都要放在store里嗎?

TAG:React | Redux |