1. 处理更新阻塞

React Router有许多位置感知(location-aware)组件,它们使用当前location对象来确定它们渲染的内容。默认情况下,当前location会隐式地传递给使用React的上下文模型的组件。当位置发生变化时,这些组件应该使用来自上下文的新的location对象重新渲染。

React提供了两种方法来优化应用程序的渲染性能:shouldComponentUpdate 生命周期方法和PureComponent。除非满足了合适的条件,否则都将阻止组件的重新渲染。不幸的是,这意味着,如果它们的重新渲染被阻止React Router的位置感知组件可能会变得与当前位置不同步。

有问题的例子

我们从一个阻止更新的组件开始。

class UpdateBlocker extends React.PureComponent {
  render() {
    return this.props.children
  }
}

<UpdateBlocker>在挂载时,任何位置感知子组件将使用当前的locationmatch对象来渲染。

// location = { pathname: '/about' }
<UpdateBlocker>
  <NavLink to='/about'>About</NavLink>
  // <a href='/about' class='active'>About</a>
  <NavLink to='/faq'>F.A.Q.</NavLink>
  // <a href='/faq'>F.A.Q.</a>
</UpdateBlocker>

当位置发生改变,<UpdateBlocker>检测不到任何属性或者状态的变化,所以他们的子组件将不会重新渲染。

// location = { pathname: '/faq' }
<UpdateBlocker>
  // 这些links不会被重新渲染,所以他们保留之前的属性
  <NavLink to='/about'>About</NavLink>
  // <a href='/about' class='active'>About</a>
  <NavLink to='/faq'>F.A.Q.</NavLink>
  // <a href='/faq'>F.A.Q.</a>
</UpdateBlocker>

shouldComponentUpdate

为了让组件知道它应该在位置发生改变时更新,需要实现它的shouldComponentUpdate方法,使该方法能够检测位置的变化。

如果你要自己实现shouldComponentUpdate,你可以比较当前的location和下一个context.router对象的location。然而,作为一个用户,您不应该直接使用上下文。相反,如果您可以比较当前和下一个位置而不触及上下文,则是理想的选择。

三方代码

您可能会遇到一些问题,组件在位置更改之后没有更新,尽管您自己没有调用shouldComponentUpdate。这很有可能是因为shouldComponentUpdate被第三方代码调用,例如:react-reduxconnectmobx-reactobserver.

// react-redux
const MyConnectedComponent = connect(mapStateToProps)(MyComponent)

// mobx-react
const MyObservedComponent = observer(MyComponent)

有了第三方代码,您可能甚至无法控制shouldComponentUpdate的实现。相反,您必须构造您的代码,以使位置更改对那些方法很显而易见。

connectobserver都创建了一个组件,该组件的shouldComponentUpdate方法对当前的props和下一个props进行了比较浅的比较。当至少有一个prop发生改变时,这些组件才会重新呈现。这意味着,为了确保在位置更改时更新,需要在位置更改时提供一个prop

PureComponent

响应的PureComponent没有实现shouldComponentUpdate,但是它采取了类似的方法来防止更新。当一个"pure"组件更新时,它将对当前的propsstate和下一个propsstate做一个比较浅的比较。如果比较没有发现任何差异,那么组件将不会更新。就像shouldComponentUpdate一样,这意味着为了在位置更改时强制"pure"组件更新,它需要有一个已更改的prop或state。

解决方案

快速解决方案

如果您在使用诸如connect(来自react-redux)或observer(来自Mobx)之类的高阶组件时遇到这个问题,您可以将该组件包装在withRouter中,以移除被阻塞的更新。

// redux before
const MyConnectedComponent = connect(mapStateToProps)(MyComponent)
// redux after
const MyConnectedComponent = withRouter(connect(mapStateToProps)(MyComponent))

// mobx before
const MyConnectedComponent = observer(MyComponent)
// mobx after
const MyConnectedComponent = withRouter(observer(MyComponent))

这不是最有效的解决方案, 但是,它将预防更新被阻塞的问题。有关这个解决方案的更多信息,参阅 Redux指南. 要理解为什么这不是最优解,轻阅读 这个线程.

推荐解决方案

在位置更改之后,避免重新渲染被阻塞的关键是将location对象传递给被阻塞的组件作为一个属性。无论何时位置发生变化,location将是不同的,所以比较程序会发现当前和下一个location是不同的。

// location = { pathname: '/about' }
<UpdateBlocker location={location}>
  <NavLink to='/about'>About</NavLink>
  // <a href='/about' class='active'>About</a>
  <NavLink to='/faq'>F.A.Q.</NavLink>
  // <a href='/faq'>F.A.Q.</a>
</UpdateBlocker>

// location = { pathname: '/faq' }
<UpdateBlocker location={location}>
  <NavLink to='/about'>About</NavLink>
  // <a href='/about'>About</a>
  <NavLink to='/faq'>F.A.Q.</NavLink>
  // <a href='/faq' class='active'>F.A.Q.</a>
</UpdateBlocker>

获取location

为了将当前的location对象作为属性传递给组件,您必须能够访问它。组件可以访问该location的主要方式是通过一个<Route>。当一个<Route>匹配(或者总是使用children属性)时,它将当前location传递给它渲染的子元素。

<Route path='/here' component={Here}/>
const Here = (props) => {
  // props.location = { pathname: '/here', ... }
  return <div>You are here</div>
}

<Route path='/there' render={(props) => {
  // props.location = { pathname: '/there', ... }
  return <div>You are there</div>
}}/>

<Route path='/everywhere' children={(props) => {
  // props.location = { pathname: '/everywhere', ... }
  return <div>You are everywhere</div>
}}/>

这意味着,如果给定一个阻止更新的组件,用下列方法,您可以很容易地将location作为它一个属性传递:

// Blocker是一个 "pure"组件,所以它仅在接收到新props时更新 
class Blocker extends React.PureComponent {
  render() {
    <div>
      <NavLink to='/oz'>Oz</NavLink>
      <NavLink to='/kansas'>Kansas</NavLink>
    </div>
  }
}
  1. 一个直接由一个<Route>渲染的组件不需要担心更新被阻塞,因为它有一个被注入的location作为一个属性。
// 无论这个location什么改变,<Blocker>的lacation属性将改变
<Route path='/:place' component={Blocker}/>
  1. <Route>直接渲染的组件可以将该位置属性传递给它所创建的任何子元素。
<Route path='/parent' component={Parent} />

const Parent = (props) => {
  // <Parent> 接收location作为它的一个prop,location会传递到它创建的任何子元素
  return (
    <SomeComponent>
      <Blocker location={props.location} />
    </SomeComponent>
  )
}

当组件没有被一个<Route>组件渲染,而该组件渲染时它没有location参数,会发生什么?有两种方法可以自动地将location作为组件的属性注入到组件中。

  1. 渲染一个没有path<Route>。虽然<Route>通常被用于匹配特定的路径,但无path<Route>总是匹配的,因此它总是渲染它的组件。
// 无`path`的 <Route> = <Blocker> 将始终被渲染
const MyComponent= () => (
  <SomeComponent>
    <Route component={Blocker} />
  </SomeComponent>
)
  1. 您可以使用withRouter高阶组件包裹一个组件,并且被包裹的组件将会被赋予当前的location 作为它的属性。 ```js // 在内部, withRouter 只渲染一个无路的 const BlockAvoider = withRouter(Blocker)

const MyComponent = () => ( ) ```

Copyright © tuzhu008 2017 all right reserved,powered by Gitbook该文件修订时间: 2017-11-26 15:52:19

results matching ""

    No results matching ""