日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

react中@withrouter_为什么 withRouter 高阶组件应该 处于最外层?

發布時間:2025/3/15 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 react中@withrouter_为什么 withRouter 高阶组件应该 处于最外层? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這篇文章的主要內容是說 React 16.3 以前的 getChildContext 這個老 Context API 存在會被 PureComponent 截斷的問題,React-Router 4.3.1 及之前版本是用這種 API 實現的,所以存在這個問題,在新版本的 React-Router 中已經沒有這個問題了。

之前在 CR 中看到立理大佬的評論,說 withRouter 高階組件應該放在最外面,不然可能會造成 url 變化了但是組件沒有渲染的情況,當時并不理解,然后照著 ReactRouter 的文檔仔細研究了一下原因。

復現不能正常渲染的情況

React 中有兩種常見提升渲染性能的方式:

  • 通過 shouldComponentUpdate 生命周期控制組件是否重新渲染
  • 使用 PureComponent
  • 下面是一個簡單的例子:

    class UpdateBlocker extends PureComponent {render() {return this.props.children;} }const App = () => (<Router><UpdateBlocker><NavLink to='/about'>About</NavLink><NavLink to='/faq'>F.A.Q.</NavLink></UpdateBlocker></Router> );

    這個 NavLink 也是 react-router-dom 里面的一個組件,它也是渲染了一個 a 標簽,比較特殊的是,當他能匹配當前 url 的時候,會默認給 a 標簽再添加一個 active 類。

    所以,我們再添加這樣一段 CSS,然后看效果:

    a {display: block; }a.active {color: red; }

    按照設想中的效果,應該是點擊 About 之后,About 變紅,點擊 F.A.Q. 后,F.A.Q. 變紅。但是現實卻是點擊之后并沒有什么效果。

    原因就是 UpdateBlocker 是一個 PureComponent,想要它重新渲染,除非它的 state 或者 props 有什么大的變動才行(淺比較結果為 false),然而在上面的例子中,UpdateBlocker 并沒有發生這種變化,所以就理所當然的不會變化了。

    shouldComponentUpdate 原理類似。所以在實現這個生命周期的時候,也要考慮 url 變動的情況。官方文檔中說可以通過 context.router 來確定 url 是否變動,但由于用戶不該直接使用 context 所以不建議這么做,而是推薦通過使用傳入 location 屬性的形式。

    解決方案

    所以,當你的 Component 是 Pure 的,應該如何處理這種情況呢?

    我簡單看了一下 react-router 的實現,當 <Link> 點擊之后,會通過 context 觸發 <Router>或者 <Route> 里面實現的相應的函數,然后在 <Router> 或 <Route> 中 setState 觸發渲染。所以不管是 <Link> 還是 withRouter 這些東西,一定要是 <Router> 或者 <Route>的后代才行。(沒理解錯吧)

    所以,如果希望 UpdateBlocker 也能正常渲染的話,只要給它傳入一個能夠觸發渲染的屬性就好了,比如 location 對象。只要想辦法在父組件拿到 location 對象,然后通過屬性給那個 Pure 的組件傳過去。當 URL 變化時,location 也會相應改變,所以也就不怕 Pure 的組件不渲染了:

    <UpdateBlocker location={location}><NavLink to='/about'>About</NavLink><NavLink to='/faq'>F.A.Q.</NavLink> </UpdateBlocker>

    那么如何讓父組件拿到 location 對象呢?

    直接通過 <Route> 渲染的組件

    如果你的組件是直接通過 <Route> 渲染的話:

    1. 一個直接通過 <Route> 渲染的組件,不需要擔心上面的問題,因為 <Route> 會自動為其包裹的組件插入 location 屬性。

    // 當 url 變化時,<Blocker> 的 location 屬性一定會變化 <Route path='/:place' component={Blocker}/>

    2. 一個直接通過 <Route> 渲染的組件,既然可以拿到 location 屬性,所以自然也可以把 location 傳給由它創建的子組件。

    <Route path='/parent' component={Parent} />const Parent = (props) => {// 既然 <Parent> 能拿到 location 屬性// 自然也可以把 location 傳給由它創建的子組件return (<SomeComponent><Blocker location={props.location} /></SomeComponent>); }

    不是直接通過 <Route> 渲染的組件

    如果一個組件不是由 <Route> 直接渲染的怎么辦呢?也有兩種辦法:

    1. 可以使用不傳 path 屬性的 <Route> 組件。<Route> 組件中的 path 屬性也不是必須的,當不傳入 path 屬性時,表示它包裹的組件總會渲染:

    // <Blocker> 組件總會渲染 const MyComponent= () => (<SomeComponent><Route component={Blocker} /></SomeComponent> );

    2. 使用 withRouter 高階組件。這個高階組件就會給它包裹的組件傳三個屬性,分別是 location、match 和 history。

    const BlockAvoider = withRouter(Blocker)const MyComponent = () => (<SomeComponent><BlockAvoider /></SomeComponent> );

    其他情況

    有時候即便你沒有使用 PureComponent 也有可能出現上面的問題,因為你有可能使用了一些實現了 shouldComponentUpdate 的高階組件,比如:

    // react-redux const MyConnectedComponent = connect(mapStateToProps)(MyComponent)// mobx-react(這個我沒用過) const MyObservedComponent = observer(MyComponent)

    這個 connect 和 observer 都實現了自己的 shouldComponentUpdate,它們也是對當前的 props 和 nextProps 淺比較,所以也會導致 即使 url 變化,也無法重新渲染 的情況。

    通過上面的分析我們也很容易找到相應的解決方案,比如:

    const MyConnectedComponent = withRouter(connect(mapStateToProps)(MyComponent))const MyConnectedComponent = withRouter(observer(MyComponent))

    其實當我看到這里的時候就已經理解為什么 withRouter 要放在最外層了。很好理解,因為如果 withRouter 在 connect 里面,即便能夠給 MyComponent 傳入 location 對象,可是渲染早在 connect 那一層就被攔截住了...

    withRouter 這個高階組件很好用,但是它并不是所有情景的最佳解決方案,還是應該視情況而定。

    因為 withRouter 本身的作用是為了給那些需要操作 route 的組件提供 location 等屬性。如果一個組件本身就已經能拿到這些屬性了,那再使用 withRouter 就是一種浪費了。

    原文中還舉了一個常見的錯誤操作,即通過 <Route> 包裹的組件,就實在沒必要包裹一層 withRouter 了:

    // 這里的 withRouter 是完全沒必要的 const MyComponent = withRouter(connect(...)(AComponent));<Route path='/somewhere' component={MyComponent} /> /** <Route path='/somewhere>* <withRouter()>* <Route>* <connect()>* <AComponent>*/

    context 與 shouldComponentUpdate 一起使用

    通過上面對于 react-router 的討論,也可以推廣至其他使用 context 的場景。

    在 React 16.3 以前,context 是一個實驗性的 API,應該是盡量避免使用的,起碼要盡量避免直接使用,雖然使用 context 實現跨級組件通信很方便。

    如果使用 context 實現了跨級組件通信,就會面臨這樣的問題:shouldComponentUpdate 或者 PureComponent 阻止了 context 的 “捕獲”。

    import React, {PureComponent, Component} from 'react'; import ReactDOM from 'react-dom'; import {bind} from 'lodash-decorators'; import PropTypes from 'prop-types';class ColorProvider extends Component {static childContextTypes = {color: PropTypes.string};getChildContext() {return {color: this.props.color};}render() {return this.props.children;} }class ColorText extends Component {static contextTypes = {color: PropTypes.string};render() {const {color} = this.context;const {children} = this.props;return (<div style={{color}}>{children}</div>);} }class TextBox extends PureComponent {render() {return <ColorText>TextBox</ColorText>} }class App extends Component {state = {color: 'red'};@bind()handleClick() {this.setState({color: 'blue'});}render() {const {color} = this.state;return (<ColorProvider color={color}><button onClick={this.handleClick}><ColorText>Click Me</ColorText></button><TextBox /></ColorProvider>);} }ReactDOM.render(<App />, document.getElementById("app"));

    上面這份代碼的效果就是顯示一個按鈕和一行文字,點擊按鈕后,按鈕顏色變藍,但是下面那行文字卻沒動靜。因為 TextBox 組件是 Pure 的,點擊按鈕后,它的 state 和 props 都沒有變化,所以自然沒有重新渲染。也就是說,這個 Pure 的組件把 context 的傳遞給攔截住了。

    如何讓 conext 和 shouldComponent 一起工作呢?我查了相關資料之后,得到了以下兩種辦法:

    1. shouldComponentUpdate 是支持第三個參數的...如果 contextTypes 在組件中定義,下列的生命周期方法將接受一個額外的參數, 就是 context 對象:

    constructor(props, context) componentWillReceiveProps(nextProps, nextContext) shouldComponentUpdate(nextProps, nextState, nextContext) componentWillUpdate(nextProps, nextState, nextContext) componentDidUpdate(prevProps, prevState, prevContext)

    2. 使用一種類似 EventEmitter 這樣的東西實現:

    import React, {PureComponent, Component} from 'react'; import ReactDOM from 'react-dom'; import {bind} from 'lodash-decorators'; import PropTypes from 'prop-types';class Color {constructor(value) {this.value = value;this.depends = [];}depend(f) {this.depends.push(f);}setValue(value) {this.value = value;this.depends.forEach(f => f());} }class ColorProvider extends Component {static childContextTypes = {color: PropTypes.object};getChildContext() {return {color: this.props.color};}render() {return this.props.children;} }class ColorText extends Component {static contextTypes = {color: PropTypes.object};componentDidMount() {this.context.color.depend(() => this.forceUpdate());}render() {const {color} = this.context;const {children} = this.props;return (<div style={{color: color.value}}>{children}</div>);} }class TextBox extends PureComponent {render() {return <ColorText>TextBox</ColorText>} }class App extends Component {color = new Color('red');@bind()handleClick() {this.color.setValue('blue');}render() {return (<ColorProvider color={this.color}><button onClick={this.handleClick}><ColorText>Click Me</ColorText></button><TextBox /></ColorProvider>);} }ReactDOM.render(<App />, document.getElementById("app"));

    更好的做法是,不要把 context 當做 state 用,而是把它作為一個 dependency:

    Context should be used as if it is received only once by each component.

    在 React 16.3 之后,context 有了正經的新 API,在新的 context API 中,使用 React.createContext(defaultValue) 這個 API 來創建 context 對象,使用 context 對象中的 <Provider /> 和 <Consumer /> 來操作 context。并且當 Provider 的值改變時,所有的 Consumer 也都會重新渲染,所以說,在新的 API 中,已經輕松避免了上述問題...

    總結

    以上是生活随笔為你收集整理的react中@withrouter_为什么 withRouter 高阶组件应该 处于最外层?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。