TIP
原文:在你写 memo()之前
大概意思就是在你写memo()
去优化组件的时候还有两种方式去优化代码。
import { useState } from 'react'
export default function App() {
let [color, setColor] = useState('red')
return (
<div>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
<ExpensiveTree />
</div>
)
}
function ExpensiveTree() {
let now = performance.now()
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>
}
上面这个组件现在存在的问题:
input
中输入color
后,会导致App
组件重新渲染,然后ExpensiveTree
组件虽然不依赖color
,但是由于父组件re-render
,他自己也会进行无效的re-render
为了减少这种无效的re-render
,我们经常会使用memo()去包裹组件,来达到缓存组件,减少无效更新的情况。
import { useState, memo } from 'react'
export default function App() {
let [color, setColor] = useState('red')
return (
<>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
<ExpensiveTree />
</>
)
}
// 使用memo
let ExpensiveTree = memo(() => {
let now = performance.now()
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>
})
而这篇文章就是讲解另外两种解决思路。
这个解决方法其实就是将组件粒度变得更细。
将state
下沉到与之相关的组件中去,也就是将与该状态相关的组件抽离成一个单独的组件。
import { useState } from 'react'
export default function App() {
return (
<div>
<Form />
<ExpensiveTree />
</div>
)
}
// 将state下沉到该组件中
function Form() {
let [color, setColor] = useState('red')
return (
<>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
</>
)
}
function ExpensiveTree() {
let now = performance.now()
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>
}
像上面那种情况,组件可以单独抽离是因为知道ExpensiveTree
组件不依赖color
的状态。
但是如果我们假设是App
中的div
依赖color
呢。这种情况下其实ExpensiveTree
组件依然不应该刷新。
import { useState } from 'react'
export default function App() {
let [color, setColor] = useState('red')
return (
<div style={{ color }}>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p>Hello, world!</p>
<ExpensiveTree />
</div>
)
}
function ExpensiveTree() {
let now = performance.now()
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>
}
这种情况下,我们需要将
import { useState } from 'react'
export default function App() {
return (
<ColorPicker>
<p>Hello, world!</p>
<ExpensiveTree />
</ColorPicker>
)
}
// 将内容提升到该父组件中
function ColorPicker({ children }) {
let [color, setColor] = useState('red')
return (
<div style={{ color }}>
<input value={color} onChange={(e) => setColor(e.target.value)} />
{children}
</div>
)
}
function ExpensiveTree() {
let now = performance.now()
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>
}
可以看到我们将 App 组件一分为二。
将于color
相关的组件放到ColorPicker
中,然后不相关的作为children
传给ColorPicker
组件。
这样在ColorPicker
组件re-render
的时候,由于**App**
(父组件)中的组件没有变化,所以拿到的**children**
依然是上一次的(没有发生变化的)所以**children**
部分不会**re-render**
。
这样就避免了无效的刷新。
其实我理解的这里的内容提升,是指将于本次re-render
无关的组件提升到父组件中去,通过props
的方法传递给其他组件。这样其他组件在进行re-render
的时候其实是不会影响到props
的。
这里我们讲完了上面的两种方法,来看一个案例。加深一下印象
import React, { ReactNode, StrictMode } from 'react'
import { useValue, MyContext } from './state'
import Counter from './Counter'
import Person from './Person'
const Provider = ({ children }: { children: ReactNode }) => <MyContext.Provider value={useValue()}>{children}</MyContext.Provider>
const Body = () => (
<div>
<h1>Counter</h1>
<Counter />
<Counter />
<h1>Person</h1>
<Person />
<Person />
</div>
)
const App = () => (
<StrictMode>
<Provider>
<Body />
</Provider>
</StrictMode>
)
export default App
这里省略了很多代码,具体的代码案例,查看上面的链接。
一句话来说。这是 use-context-seletor
的官方例子。点击 +
或者-
按钮,下面 Person
表单不会刷新。
但是将此案例稍微更改一下就会发现不一样的效果:
Provider
组件import React, { ReactNode, StrictMode } from 'react'
import { useValue, MyContext } from './state'
import Counter from './Counter'
import Person from './Person'
const Body = () => (
<div>
<h1>Counter</h1>
<Counter />
<Counter />
<h1>Person</h1>
<Person />
<Person />
</div>
)
const App = () => (
<StrictMode>
<MyContext.Provider value={useValue()}>
<Body />
</MyContext.Provider>
</StrictMode>
)
export default App
会发现这时点击 +
或者-
按钮,Person
组件也会进行re-render
。
Provider
组件之后。当MyContext.Provider
组件的value
值更新时,其会进行re-reder
,那么作为子组件的Body
自然也会进行re-render
。那么在Body
组件里面的子组件也会进行re-render
re-render
呢?
Body
组件是作为props.children
传递给Provider
组件的,当MyContext.Provider
组件的value
值更新时,会触发其进行re-render
,但是props
不会受到它的影响,所以Body
组件没有进行re-render
,那么父组件没有进行re-render
他里面的子组件自然不会进行无效的re-render
.state
。在不变中抽离中变化的部分,将state
与变化的部分绑定为同一个组件。re-render
是不会影响props
的,即与props
无关。所以我们可以通过props
的方法传递无关的组件,来避免re-render
。
props.children
之外那用其他 props
属性可以吗?比如 <Changed left={<Expansive1 />} right={<Expansive2 />} />
,<Changed />``re-render
并不会导致 <Expansive />`` re-render
。这种方法叫「componets as props
」。