摘要:的作用在文檔中是這么說的給下級組件中的提供可用的的對象。這個文件里的主要是被方法引入,并傳給的,算是一個默認的。表示當前的名稱。這個值表示在里面的值。便于控制,同時某些不需要渲染的,也不會造成渲染。
注:這篇文章只是講解React Redux這一層,并不包含Redux部分。Redux有計劃去學習,等以后學習了Redux源碼以后再做分析
注:代碼基于現在(2016.12.29)React Redux的最新版本(5.0.1)
Connect工具類篇(1)
Connect工具類篇(2)
在5.0.1版本中,React Redux提供了兩個Components,一個是Provider,另外一個是connectAdvanced。
connect應該也算一個,它設置了一些需要的默認值,并調用、返回connectAdvanced。
Provider的作用在文檔中是這么說的
Props給下級組件中的connect()提供可用的Redux的store對象。一般情況下,如果根組件沒有被
包裹,那么你就無法使用connect()方法。 如果你堅持不用
,你也可以給每一個需要connect()的組件手動傳遞store屬性。但是我們只建議在unit tests或者非完全React的項目中這么用,否則應該使用 。
根據文檔,屬性應該包含store和children:
store (Redux Store): The single Redux store in your application.
children (ReactElement) The root of your component hierarchy.
先貼一個使用示例:
源碼中也對propTypes做了定義(storeShape請看這里)
Provider.propTypes = { store: storeShape.isRequired, // store必須含有storeShape (subscribe, dispatch, getState) children: PropTypes.element.isRequired // children必須是一個React元素 }之所以文檔中說:給下級組件中的connect()提供可用的Redux的store對象
是因為Provider里面給下級組件在context中添加了store對象,所以下級所有組件都可以拿到store.
export default class Provider extends Component { getChildContext() { return { store: this.store } // 給下級組件添加store } constructor(props, context) { super(props, context) this.store = props.store } render() { return Children.only(this.props.children) // 渲染children } } Provider.childContextTypes = { store: storeShape.isRequired }在源碼中還有一點是關于hot reload reducers的問題:
let didWarnAboutReceivingStore = false function warnAboutReceivingStore() { if (didWarnAboutReceivingStore) { return } didWarnAboutReceivingStore = true warning( "does not support changing `store` on the fly. " + "It is most likely that you see this error because you updated to " + "Redux 2.x and React Redux 2.x which no longer hot reload reducers " + "automatically. See https://github.com/reactjs/react-redux/releases/" + "tag/v2.0.0 for the migration instructions." ) } if (process.env.NODE_ENV !== "production") { Provider.prototype.componentWillReceiveProps = function (nextProps) { const { store } = this const { store: nextStore } = nextProps if (store !== nextStore) { warnAboutReceivingStore() } } }
好像是React Redux不支持hot reload,根據里面提供的鏈接,發現hot reload會造成錯誤,所以在2.x的時候進行了修改,使用replaceReducer的方法來初始化App。具體可以看這里,還有這里。
我并不知道怎么重現這個,我自己在Hot reload下,修改了reducer和action,但是并沒有出現這個warning...(懵逼臉
調用方法:
connectAdvanced(selectorFactory, options)(MyComponent)
文檔這么介紹的:
把傳入的React組件和Redux store進行連接。這個方法是connect()的基礎,但是相比于connect()缺少了合并state, props和dispatch的方法。它不包含一些配置的默認值,還有一些便于優化的結果對比。這些所有的事情,都要有調用者來解決。
這個方法不會修改傳入的組件,而是在外面包裹一層,生成一個新的組件。
這個方法需要兩個參數:
selectorFactory 大概的格式是這樣子的selectorFactory(dispatch, factoryOptions)=>selector(state, ownProps)=>props。每次Redux store或者父組件傳入的props發生改變,selector方法就會被調用,重新計算新的props。最后的結果props應該是一個plain object,這個props最后會被傳給包裹的組件。如果返回的props經過對比(===)和上一次的props是一個對象,那么組件就不會被re-render。所以如果符合條件的話,selector應該返回同一個對象而不是新的對象(就是說,如果props內容沒有發生改變,那么就不要重新生成一個新的對象了,直接用之前的對象,這樣可以保證===對比返回true)。
注:在之前的文章中,介紹了selectorFactory.js這個文件的內容。這個文件里的selectorFactory主要是被connect()方法引入,并傳給connectAdvanced的,算是一個默認的selector。
connectOptions 這個是非必須參數,中間包含幾個參數:
getDisplayName(function) 用處不大,主要是用來表示connectAdvanced組件和包含的組件的關系的。比如默認值是name=>"ConnectAdvanced(" + name + ")"。同時如果用connect()的話,那么這個參數會在connect中被覆蓋成connect(name)。這個的結果主要是在selectorFactory中驗證拋出warning時使用,會被加入到connectOptions一起傳給selectorFactory。
methodName(string) 表示當前的名稱。默認值是"connectAdvanced",如果使用connect()的話,會被覆蓋成"connect"。也是被用在拋出warning的時候使用
renderCountProp(string) 這個主要是用來做優化的時候使用。如果傳入了這個string,那么在傳入的props中會多加一個prop(key是renderCountProps的值)。這個值就可以讓開發獲取這個組件重新render的次數,開發可以根據這個次數來減少過多的re-render.
shouldHandleStateChanges(Boolean) 默認值是true。這個值決定了Redux Store State的值發生改變以后,是否re-render這個組件。如果值為false,那么只有在componentWillReceiveProps(父組件傳遞的props發生改變)的時候才會re-render。
storeKey(string) 一般不要修改這個。默認值是"store"。這個值表示在context/props里面store的key值。一般只有在含有多個store的時候,才需要用這個
withRef(Boolean) 默認值是false。如果是true的話,父級可以通過connectAdvanced中的getWrappedInstance方法來獲取組件的ref。
還有一些其他的options, 這些options都會通過factoryOptions傳給selectorFactory進行使用。(如果用的是connect(),那么connect中的options也會被傳入)
注:withRef中所謂的父級可以通過getWrappedInstance方法來獲取,可以看看下面的代碼(從stackoverflow拿的):
class MyDialog extends React.Component { save() { this.refs.content.getWrappedInstance().save(); } render() { return ( ); } } class Content extends React.Component { save() { ... } } function mapStateToProps(state) { ... } module.exports = connect(mapStateToProps, null, null, { withRef: true })(Content);
注:由于我對hot reload的運行方法不是很了解。。。所以代碼里的hot reload的地方我就不說了。。。
代碼太長,而且不復雜,我直接把解釋寫到注釋里:
let hotReloadingVersion = 0 export default function connectAdvanced( selectorFactory, { getDisplayName = name => `ConnectAdvanced(${name})`, methodName = "connectAdvanced", renderCountProp = undefined, shouldHandleStateChanges = true, storeKey = "store", withRef = false, ...connectOptions } = {} ) { const subscriptionKey = storeKey + "Subscription" // subscription的key const version = hotReloadingVersion++ // hot reload version const contextTypes = { [storeKey]: storeShape, // 從Provider那里獲取的store的type [subscriptionKey]: PropTypes.instanceOf(Subscription), // 從上級獲取的subscription的type } const childContextTypes = { [subscriptionKey]: PropTypes.instanceOf(Subscription) // 傳遞給下級的subscription的type } return function wrapWithConnect(WrappedComponent) { // 負責檢查wrappedComponent是否是function,如果不是拋出異常 invariant( typeof WrappedComponent == "function", `You must pass a component to the function returned by ` + `connect. Instead received ${WrappedComponent}` ) const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || "Component" const displayName = getDisplayName(wrappedComponentName) // 用于異常拋出的名字 const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, withRef, displayName, wrappedComponentName, WrappedComponent } // 如果之前傳入的組件叫做wrappedComponent, 這個Connect組件應該叫wrapComponent,用來包裹wrappedComponent用的 class Connect extends Component { constructor(props, context) { super(props, context) // 初始化一些信息 this.version = version this.state = {} this.renderCount = 0 this.store = this.props[storeKey] || this.context[storeKey] // 獲取store,有props傳入的是第一優先級,context中的是第二優先級。 this.parentSub = props[subscriptionKey] || context[subscriptionKey] // 獲取context this.setWrappedInstance = this.setWrappedInstance.bind(this) // 綁定this值,然而不知道有什么用。。。難道怕別人搶了去? // 判斷store是否存在 invariant(this.store, `Could not find "${storeKey}" in either the context or ` + `props of "${displayName}". ` + `Either wrap the root component in a, ` + `or explicitly pass "${storeKey}" as a prop to "${displayName}".` ) this.getState = this.store.getState.bind(this.store); // 定義一個getState方法獲取store里面的state this.initSelector() this.initSubscription() } // 把當前的subscription傳遞給下級組件,下級組件中的connect就可以把監聽綁定到這個上面 getChildContext() { return { [subscriptionKey]: this.subscription } } componentDidMount() { if (!shouldHandleStateChanges) return this.subscription.trySubscribe() this.selector.run(this.props) if (this.selector.shouldComponentUpdate) this.forceUpdate() } componentWillReceiveProps(nextProps) { this.selector.run(nextProps) } // shouldComponentUpdate只有跑過run方法的時候才會是true // run方法只有在Redux store state或者父級傳入的props發生改變的時候,才會運行 shouldComponentUpdate() { return this.selector.shouldComponentUpdate } // 把一切都復原,這樣子可以有助于GC,避免內存泄漏 componentWillUnmount() { if (this.subscription) this.subscription.tryUnsubscribe() // these are just to guard against extra memory leakage if a parent element doesn"t // dereference this instance properly, such as an async callback that never finishes this.subscription = null this.store = null this.parentSub = null this.selector.run = () => {} } // 通過這個方法,父組件可以獲得wrappedComponent的ref getWrappedInstance() { invariant(withRef, `To access the wrapped instance, you need to specify ` + `{ withRef: true } in the options argument of the ${methodName}() call.` ) return this.wrappedInstance } setWrappedInstance(ref) { this.wrappedInstance = ref } initSelector() { const { dispatch } = this.store const { getState } = this; const sourceSelector = selectorFactory(dispatch, selectorFactoryOptions) // 注意這里不會進行任何的setState和forceUpdate,也就是說這里不會重新渲染 // 在這里會記錄上一個props,并和更新后的props進行對比,減少re-render次數 // 用shouldComponentUpdate來控制是否需要re-render const selector = this.selector = { shouldComponentUpdate: true, props: sourceSelector(getState(), this.props), run: function runComponentSelector(props) { try { const nextProps = sourceSelector(getState(), props) // 獲取最新的props if (selector.error || nextProps !== selector.props) { // 進行對比,如果props發生了改變才改變props對象,并把可渲染flag設為true selector.shouldComponentUpdate = true selector.props = nextProps selector.error = null } } catch (error) { selector.shouldComponentUpdate = true // 如果有錯誤也會把錯誤信息渲染到頁面上 selector.error = error } } } } initSubscription() { // 如果組件不依據redux store state進行更新,那么根本不需要監聽上級的subscription if (shouldHandleStateChanges) { // 建立一個自己的subscription const subscription = this.subscription = new Subscription(this.store, this.parentSub) const dummyState = {} // 隨便的state, 主要就是用來調用setState來re-render的 subscription.onStateChange = function onStateChange() { this.selector.run(this.props) // 每次redux state發生改變都要重新計算一遍 if (!this.selector.shouldComponentUpdate) { // 如果當前組件的props沒有發生改變,那么就只通知下級subscription就好 subscription.notifyNestedSubs() } else { // 如果發生了改變,那么就在更新完以后,再通知下級 this.componentDidUpdate = function componentDidUpdate() { this.componentDidUpdate = undefined subscription.notifyNestedSubs() } // re-render this.setState(dummyState) } }.bind(this) } } // 判斷是否監聽了上級subscription isSubscribed() { return Boolean(this.subscription) && this.subscription.isSubscribed() } // 加入多余的props,注意使用props的影對象進行操作,避免把ref添加到selector中,造成內存泄漏 addExtraProps(props) { if (!withRef && !renderCountProp) return props const withExtras = { ...props } if (withRef) withExtras.ref = this.setWrappedInstance if (renderCountProp) withExtras[renderCountProp] = this.renderCount++ return withExtras } render() { const selector = this.selector selector.shouldComponentUpdate = false if (selector.error) { throw selector.error } else { return createElement(WrappedComponent, this.addExtraProps(selector.props)) } } } Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName Connect.childContextTypes = childContextTypes Connect.contextTypes = contextTypes Connect.propTypes = contextTypes if (process.env.NODE_ENV !== "production") { Connect.prototype.componentWillUpdate = function componentWillUpdate() { // We are hot reloading! if (this.version !== version) { this.version = version this.initSelector() if (this.subscription) this.subscription.tryUnsubscribe() this.initSubscription() if (shouldHandleStateChanges) this.subscription.trySubscribe() } } } return hoistStatics(Connect, WrappedComponent) } }
需要注意的:
在組件中, this.store = this.props[storeKey] || this.context[storeKey]; this.parentSub = props[subscriptionKey] || context[subscriptionKey];, 所以props中的store和subscription都是優先于context的。所以,如果你決定使用不同的store或者subscription,可以在父組件中傳入這個值。
connectconnect方法是react-redux最常用的方法。這個方法其實是調用了connectAdvanced方法,只不過和直接調用不同的是,這里添加了一些參數的默認值。
而且connectAdvanced方法接受的是selectorFactory作為第一個參數,但是在connect中,分為mapStateToProps, mapDispatchToProps, mergeProps三個參數,并多了一些pure, areStateEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual這些配置。所有的這些多出來的參數都是用于根據selectorFactory.js制造一個簡單的selectorFactory
調用方法:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
先看兩個輔助用的方法:
function match(arg, factories, name) { for (let i = factories.length - 1; i >= 0; i--) { const result = factories[i](arg) if (result) return result } return (dispatch, options) => { throw new Error(`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${options.wrappedComponentName}.`) } } function strictEqual(a, b) { return a === b }
match之前已經在說mapDispatchToProps.js的時候已經提到,這里就不說了。strictEqual就是一個簡單的絕對相等的封裝。
主題代碼是這樣子的:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) { return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, "mapStateToProps") const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, "mapDispatchToProps") const initMergeProps = match(mergeProps, mergePropsFactories, "mergeProps") return connectHOC(selectorFactory, { // used in error messages methodName: "connect", // used to compute Connect"s displayName from the wrapped component"s displayName. getDisplayName: name => `Connect(${name})`, // if mapStateToProps is falsy, the Connect component doesn"t subscribe to store state changes shouldHandleStateChanges: Boolean(mapStateToProps), // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions }) } } export default createConnect()createConnect方法
其中,createConnect方法是一個factory類的方法,主要是對一些需要的factory進行默認初始化。
export function createConnect({ connectHOC = connectAdvanced, // connectAdvanced的方法 mapStateToPropsFactories = defaultMapStateToPropsFactories, // mapStateToProps.js mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, // mapDispatchToProps.js mergePropsFactories = defaultMergePropsFactories, // mergeProps.js selectorFactory = defaultSelectorFactory // selectorFactory.js } = {}) { // ... }
由于這個方法也是export的,所以其實由開發進行調用,可以自定義自己的factory方法,比如你或許可以這么用:
var myConnect = createConnect({ connectHOC: undefined, // 使用connectAdvanced mapStateToPropsFactories: myMapToStatePropsFactories, //..... }); myConnect(mapStateToProps, mapDispatchToProps, options)(myComponnet);
不過這個方法并沒有在文檔中提到,可能是官方認為,你寫這么多factories,還不如用connectAdvanced自己封裝一個selectorFactory來的方便。
connect方法在內層的connect方法中,除了對幾個對比方法進行初始化,主要是針對factories根據傳入的參數進行封裝、操作。
function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { // ....... }
這里的pure參數和equal參數都在前兩篇中有詳細的描述(connect工具類1, connect工具類2),可以在那里看看。
提一點,項目中可以通過根據不同的情況優化...Equal的四個方法來優化項目,減少必不要的重新渲染,因為如果這個*Equal方法驗證通過,就不會返回新的props對象,而是用原來儲存的props對象(對某些層級比較深的情況來說,即使第一層內容相同,shallowEqual也會返回false,比如shallowEqual({a: {}}, {a: {}})),那么在connectAdvanced中就不會重新渲染。
connect內部實現const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, "mapStateToProps") const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, "mapDispatchToProps") const initMergeProps = match(mergeProps, mergePropsFactories, "mergeProps") return connectHOC(selectorFactory, { methodName: "connect", // 覆蓋connectAdvanced中的methodName, 用于錯誤信息顯示 getDisplayName: name => `Connect(${name})`, // 覆蓋connectAdvanced中的getDisplayName, 用于錯誤信息顯示 shouldHandleStateChanges: Boolean(mapStateToProps), // 如果mapStateToProps沒有傳,那么組件就不需要監聽redux store // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions })
中間需要提一點,就是shouldHandleStateChanges的這個屬性。根據文檔中對mapStateToProps的介紹,有一句話是:
mapStateToProps 如果這個沒有傳這個參數,那么組件就不會監聽Redux store.
其實原因很簡單,由于connect中只有mapStateToProps(state, [ownProps])是根據redux store state的改變進行改變的,而像mapDispatchToProps(dispatch, [ownProps])和mergeProps(stateProps, dispatchProps, ownProps)都和redux store無關,所以如果mapStateToProps沒有傳的話,就不需要去監聽redux store。
一點總結: 可以怎么去做性能優化?除了最最基礎的shouldComponentUpdate之外,針對Redux React,我們可以通過優化areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual四個方法,來確保特殊情況下,props的對比更精確。
pure盡量使用默認的true,只有在內部的渲染會根據除了redux store和父組件傳入的props之外的狀態進行改變,才會使用false。但是false會造成忽略上面的對比,每次改變都進行重新渲染
mapStateToProps, mapDispatchToProps如果不需要ownProps參數,就不要寫到function定義中,減少方法的調用次數。
如果mapStateToProps不需要的話,就不傳或者undefined,不要傳noop的function,因為noop方法也會讓shouldHandleStateChanges為true,平白讓connect多了一個監聽方法。
自定義store之前有提到,react redux是接受自定義store的。也就是說你可以從父組件傳入一個store給connect組件,connect組件就會優先使用這個store。但是store必須有一定的格式,比如里面需要有一個getState方法來獲取state。
加個參數來控制是否渲染在connectAdvanced里面,他們使用了selector.shouldComponentUpdate來控制是否需要渲染,然后在React的shouldComponentUpdate里面返回這個屬性。這個方法的優點就是,就像一個開關,當需要渲染的時候再打開,不需要渲染或者渲染后關閉開關。便于控制,同時某些不需要渲染的setState,也不會造成渲染。
一個獲取子組件中的component ref的小方法在看getWrappedInstance方法的時候,在github上面看到原作者的一個小方法,可以用來獲取子組件中的component。
代碼很清晰,只是有的時候想不到,直接上代碼:
class MyComponent extends Component { render() { return (); } } class ParentComponent extends Component { componentDidMount() { this.input.focus(); } render() { return (this.input = input} /> ) } }
用這種方法,就可以把input的ref直接傳遞給parentComponent中,在parentComponent中就可以直接對Input進行操作。這個方法對用connect包裹后的組件同樣有效。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/81121.html
摘要:另外,內置的函數在經過一系列校驗后,觸發,之后被更改,之后依次調用監聽,完成整個狀態樹的更新。總而言之,遵守這套規范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進階漫談的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/s...
摘要:另外,內置的函數在經過一系列校驗后,觸發,之后被更改,之后依次調用監聽,完成整個狀態樹的更新。總而言之,遵守這套規范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進階漫談的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/s...
摘要:更多相關介紹請看這特點僅僅只是虛擬最大限度減少與的交互類似于使用操作單向數據流很大程度減少了重復代碼的使用組件化可組合一個組件易于和其它組件一起使用,或者嵌套在另一個組件內部。在使用后,就變得很容易維護,而且數據流非常清晰,容易解決遇到的。 歡迎移步我的博客閱讀:《React 入門實踐》 在寫這篇文章之前,我已經接觸 React 有大半年了。在初步學習 React 之后就正式應用到項...
注:這篇文章只是講解React Redux這一層,并不包含Redux部分。Redux有計劃去學習,等以后學習了Redux源碼以后再做分析注:代碼基于現在(2016.12.29)React Redux的最新版本(5.0.1) Utils篇 這一小節里面先把基礎的Utils代碼過一遍,以后看核心代碼的時候方便一點。由于是Utils不涉及文檔,所以沒有文檔方面的展示 shallowEqual.js 從名...
摘要:不只為組件提供中的數據及擴展方法,它還為定義的組件添加了一系列事件操作,這些事件的核心點就是,然后可以在自己定義的組件內獲得。行為功能是對目的功能和有用行為的一種抽象。下一個中間件函數通常由名為的變量來表示。 redux 這個是好久之前寫的,一直忘記粘過來,里面有一些是寫作格式是我自己定義的,所以和segmentfault的markdown語法有出入,圖片也不能加載,所以原文效果可以在...
閱讀 3290·2021-09-09 11:39
閱讀 1237·2021-09-09 09:33
閱讀 1139·2019-08-30 15:43
閱讀 555·2019-08-29 14:08
閱讀 1741·2019-08-26 13:49
閱讀 2386·2019-08-26 10:09
閱讀 1553·2019-08-23 17:13
閱讀 2291·2019-08-23 12:57