反应组件和渲染之间的路由器差异

时间:2018-01-08 12:43:06

标签: reactjs react-router

我真的没有区分反应路由器中的路由器中的渲染和组件道具,在文档中它表示渲染不会创建新元素但组件确实如此,我试图回溯历史但我发现componentWillMount是当我在Route中使用render时调用它们是什么意思“如果你为组件属性提供内联函数,你将在每次渲染时创建一个新组件。这会导致现有组件卸载和新组件安装而不仅仅是更新现有的组件。“

4 个答案:

答案 0 :(得分:43)

The source code说明不同之处:

if (component)
  return match ? React.createElement(component, props) : null

if (render)
  return match ? render(props) : null

当您使用component prop时,每次调用Route#render时都会实例化该组件。这意味着,对于传递给Route component prop的组件,构造函数componentWillMountcomponentDidMount将在每次呈现路径时执行。

例如,如果你有

<Route path="/:locale/store" component={Store} />

并且用户导航到/ en / store,然后转到其他地方,然后导航回/ en / store,Store组件将被挂载,然后卸载,然后再次挂载。它类似于做

<Route path="/:locale/store">
  <Store />
</Route>

与此相比,如果您使用render prop,则每个Route#render上的组件都会评估。还记得每个组件都是一个功能吗?此函数将按原样执行,无需任何生命周期方法。所以当你拥有它时

<Route path="/:locale/store" render={Store} />

您可以将其视为

<Route path="/:locale/store">
  {Store()}
</Route>

它保存了运行时,因为没有运行生命周期方法,但它也有一个缺点,例如,Store组件有一些post-mount生命周期方法,如shouldComponentUpdate,也可以提高性能。

a good post on Medium about this performance hack,请看一下。它编写得非常好,也适用于React 16。

答案 1 :(得分:25)

所以我也对这部分文档感到困惑,但我终于明白了。

理解这一点的关键是声明“为组件道具提供内联函数

我们都知道Route组件会在位置发生变化时重新渲染,并且react会比较旧的和新的虚拟DOM树,得到一些diff结果并应用到真正的DOM。

并且react会尝试最好重用DOM节点,除非新的ReactElement的类型道具已更改。

所以

// 1.
const componentA = React.createElement(App, props)
const componentB = React.createElement(App, props)
console.log(componentA.type === componentB.type)             // true

// 2.
const componentA = React.createElement(() => <App />, props)
const componentB = React.createElement(() => <App />, props)
console.log(componentA.type === componentB.type)             // false

通过方式1创建的所有ReactElements都具有相同的类型(App组件),但如果它们都是通过方式2创建的,则它们的类型不同。

为什么?

因为在调用父组件(包含Route组件的组件)render方法时,总是会以2的方式创建一个新的匿名函数,所以类型的new&amp; old ReactElement 是两个匿名函数的不同实例

() => <App />

所以在React的观点中,有不同的类型元素,应该用 unmount old&gt;来处理mount new 操作,这意味着每次重新渲染父组件时,您对旧组件所做的每个状态或更改都会丢失。

但为什么渲染道具避免了卸载和挂载行为?这也是一个匿名函数!?

这里我想引用@Rishat Muhametshin发布的代码,这是Route组件渲染方法的核心部分:

if (component)
  // We already know the differences:
  // React.createElement(component)
  // React.createElement(() => <component/>)
  return match ? React.createElement(component, props) : null

if (render)
  return match ? render(props) : null

render prop是一个在调用时返回ReactElement的函数,返回元素的类型是什么?

<Route render={() => <AppComponent />}></Route>

它是AppComponent,而不是匿名函数包装器!因为在jsx编译之后:

render = () => React.createElement(AppComponent)
render() = React.createElement(AppComponent)

React.createElement(render) =
  React.createElement(() => React.createElement(AppComponent))

React.createElement(render()) =
  React.createElement(React.createElement(AppComponent))

所以当你使用render而不是组件prop时,渲染prop 函数返回的元素类型在每次渲染时都不会改变,即使总是在每个parentElement.render上创建一个新的匿名函数实例()

在我看来,你可以通过为匿名函数命名来存档渲染道具与组件道具相同的行为:

// Put this line outside render method.
const CreateAppComponent = () => <AppComponent />

// Inside render method
render(){
  return <Route component={CreateAppComponent}/>
}

所以结论是,如果你直接使用component = {AppComponent},那么组件和渲染道具之间没有性能差异,如果你想为AppComponent分配一些道具,请使用 render={() => <AppComponent {...props}/> }代替component={() => <AppComponent {...props}/> }

答案 2 :(得分:6)

大多数概念已由其他答案解释,让我按以下顺序进行梳理:

首先,我们有source code

if (component)
  return match ? React.createElement(component, props) : null

if (render)
  return match ? render(props) : null

案例1:没有功能的组件

<Route path="/create" component={CreatePage} />
由于源代码中的React.createElement(CreatePage, props)而调用

React.createElement(component, props)。实例化将导致重新安装

案例2:不带功能的渲染

<Route path="/create" render={CreatePage} />

React.createElement(CreatePage, props) 已被调用,然后传递给渲染道具,然后由render(props)从源代码调用。没有实例,没有重新安装。

案例3:具有功能的组件

<Route path="/create" component={ () => <CreatePage /> } />

React.createElement(CreatePage, props)被称为两次。首先用于jsx解析(匿名函数),其次来自源代码。那么,为什么不在prop属性中执行此操作呢?

案例4:使用函数渲染

<Route path="/create" render={ () => <CreatePage /> } />

每次路由到path=/create时都有一个实例化(jsx解析)。感觉像是案例1

结论

根据这四种情况,如果要传递道具到组件,则需要使用情况#4 来防止重新安装。

<Route path="/abc" render={()=><TestWidget num="2" someProp={100}/>}/>

这与本主题有点相距甚远,因此我将official discussion留作进一步阅读。

答案 3 :(得分:0)

即使我们没有将任何道具传递给ComponentToRender,我也发现使用 render 而不是 component 有一些好处。 默认情况下,在使用 component 时,<Route \>将附加道具({ history, location, match })传递给ComponentToRender。我们也可以通过 render 回调访问此道具,但也可以忽略它。 我们为什么需要它? <Route />'s父级的任何渲染或任何导航(即使将路由更改为与以前相同的路径)也会创建新的match对象。 因此,当我们将其传递给ComponentToRender时,每次都会获得新的道具,这可能会导致一些性能问题,尤其是PureComponent