我真的不明白在react-router的路由中渲染和组件props之间的区别,在文档中它说渲染不会创建新元素但组件会,我试图回顾历史,但我发现 componentWillMount 在我时被调用在 Route 中使用渲染,它们是什么意思“如果您为组件属性提供内联函数,您将在每次渲染时创建一个新组件。这会导致现有组件卸载和新组件安装,而不是仅仅更新现有组件.”
react组件和渲染之间的路由器差异
源代码说明了区别:
if (component)
return match ? React.createElement(component, props) : null
if (render)
return match ? render(props) : null
当您使用component
prop 时,每次调用Route#render
. 这意味着,对于您传递给component
Route 的 prop、构造函数componentWillMount
、 和的组件,componentDidMount
将在每次渲染路由时执行。
例如,如果你有
<Route path="/:locale/store" component={Store} />
并且用户导航到 /en/store,然后转到其他地方,然后导航回 /en/store,Store 组件将被挂载,然后卸载,然后再次挂载。这类似于做
<Route path="/:locale/store">
<Store />
</Route>
与此相比,如果使用render
props,该组件评估每个Route#render
。还记得每个组件都是一个函数吗?此函数将按原样执行,没有任何生命周期方法。所以当你拥有它时
<Route path="/:locale/store" render={Store} />
你可以把它想象成
<Route path="/:locale/store">
{Store()}
</Route>
它节省了你的运行时间,因为没有运行生命周期方法,但它也有一个缺点,如果 Store 组件有一些像 shouldComponentUpdate 这样的 post-mount 生命周期方法也可以提高性能。
在 Medium 上有一篇关于这个性能黑客的好帖子,请看一看。它写得很好,也适用于 React 16。
所以我也对文档的这一部分感到困惑,但我终于弄明白了。
明白这是语句的关键“提供了一个内联函数到组件props”
我们都知道Route组件在位置改变时会重新渲染,react会比较新旧虚拟DOM树,得到一些diff结果并应用到真实DOM上。
并且react 会尽量重用DOM 节点,除非新的ReactElement的type或key prop 发生变化。
所以
// 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&old ReactElement的类型是匿名函数的两个不同实例
() => <App />
所以在 React 的观点中,有不同类型的元素,应该使用unmount old > mount new操作来处理,这意味着每次父组件重新渲染时,你对旧组件所做的每个状态或更改都会丢失。
但是为什么 render prop 避免了卸载和挂载行为?也是匿名函数!?
这里我想参考@Rishat Muhametshin贴的代码,Route组件的render方法的核心部分:
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 而不是 component prop 时,render prop函数返回的元素类型不会在每次渲染时改变,即使在每个 parentElement.render() 上总是创建一个新的匿名函数实例
在我看来,您可以通过为匿名函数命名来实现 render prop 对组件 prop 所做的相同行为:
// Put this line outside render method.
const CreateAppComponent = () => <AppComponent />
// Inside render method
render(){
return <Route component={CreateAppComponent}/>
}
所以结论是,如果直接使用 component={AppComponent} 的话,component 和 render prop 的性能没有区别,如果你想给 AppComponent 分配一些 props,使用
render={() => <AppComponent {...props}/> }
而不是component={() => <AppComponent {...props}/> }
大多数概念已被其他答案解释过,让我通过以下方式对其进行整理:
首先,我们有源代码:
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 解析(匿名函数),首先用于返回CreatePage
匿名函数的实例,其次来自源代码。那么为什么不在组件 prop 中这样做呢?
oligofren指出的错误:
解析 JSX 不会调用它。它只是最终创建了函数表达式。你不想做 #3 的原因是你每次都创建一个新的匿名类型,导致重新挂载 dom。
案例#4:用函数渲染
<Route path="/create" render={ () => <CreatePage /> } />
每次路由到path=/create
. 感觉像案例 #1吗?
结论
根据这四种情况,如果我们想把prop传给Component,就需要使用case #4来防止重新挂载。
<Route path="/abc" render={()=><TestWidget num="2" someProp={100}/>}/>
这离主题有点远,所以我留下官方讨论以供进一步阅读。
即使我们不向 传递任何propsComponentToRender
,我也发现使用render而不是component有一些好处。默认情况下,在使用component时<Route \>
将附加 props( { history, location, match }
)传递给。我们也可以通过渲染回调访问这个props,但我们也可以省略它。为什么我们需要它?父级或任何导航的每个渲染(即使将路由更改为与以前相同)都会创建新对象。所以当我们把它传给我们的时候,我们每次都会得到新的props,什么可能会导致一些性能问题,尤其是.ComponentToRender
<Route />'s
match
ComponentToRender
PureComponent