本文是 React 系列的第三篇前端
React 新特性 Hooks 讲解及实例(二)github
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!数组
使用 Context ,首先顶层先声明 Provier
组件,并声明 value
属性,接着在后代组件中声明 Consumer
组件,这个 Consumer
子组件,只能是惟一的一个函数,函数参数便是 Context
的负载。若是有多个 Context
,Provider
和 Consumer
任意的顺序嵌套便可。性能优化
此外咱们还能够针对任意一个 Context
使用 contextType
来简化对这个 Context
负载的获取。但在一个组件中,即便消费多个 Context
,contextType
也只能指向其中一个ide
在 Hooks 环境中,依旧可使用 Consumer
,可是 ContextType
做为类静态成员确定是用不了。Hooks 提供了 useContext
,不但解决了 Consumer 难用的问题同时也解决了 contextType
只能使用一个 context
的问题。函数
来个使用类形式的例子:post
class Foo extends Component {
render() {
return (
<CountContext.Consumer>
{
count => <h1>{count}</h1>
}
</CountContext.Consumer>
)
}
}
function App (props) {
const [count, setCount] = useState(0);
return (
<div>
<button type="button"
onClick={() => {setCount(count + 1) }}
>
Click({count})
</button>
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
</div>
)
}
复制代码
以上就不说解释了,第一篇已经讲过了,接着将 Foo
改为用 contextType
的形式:性能
class Foo extends Component {
static contextType = CountContext;
render() {
const count = this.context
return (
<h1>{count}</h1>
)
}
}
复制代码
接着使用 useContext
形式:学习
function Foo () {
const count = useContext(CountContext)
return (
<h1>{count}</h1>
)
}
复制代码
useContext
接收一个 context
对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <CountContext.Provider>
的 value prop 决定。
当组件上层最近的 <CountContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 CountContext provider 的 context value 值。
别忘记 useContext
的参数必须是 context
对象自己:
useContext
的组件总会在 context
值变化时从新渲染。若是重渲染组件的开销较大,你能够 经过使用 memoization 来优化。meno 用来优化函数组件重渲染的行为,当传入属性值都不变的状况下,就不会触发组件的重渲染,不然就会触发组件重渲染。
meno
针对的是一个组件的渲染是否重复执行,而 useMemo
定义的是一个函数逻辑是否重复执行。
来个粟子:
function Foo (props) {
return (
<h1>{props.count}</h1>
)
}
function App (props) {
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2
}, [count])
return (
<div>
<button type="button"
onClick={() => {setCount(count + 1) }}
>
Click({count}) double: ({double})
</button>
<Foo count={count}/>
</div>
)
}
复制代码
运行结果:
如上所示,useMemo
语法与 useEffect
是一致的。第一个参数是须要执行的逻辑函数,第二个参数是这个逻辑依赖输入变量组成的数组,若是不传第二个参数,这 useMemo
的逻辑每次就会运行,useMemo
自己的意义就不存在了,因此须要传入参数。因此传入空数组就只会运行一次,策略与 useEffect
是同样的,但有一点比较大的差别就是调用时机,useEffect
执行的是反作用,因此必定是渲染以后才执行,但 useMemo
是须要返回值的,而返回值能够直接参与渲染,所以 useMemo
是在渲染期间完成的。
接下来改造一下 useMemo
,让它依赖 count
以下:
const double = useMemo(() => {
return count * 2
}, [count])
复制代码
接着只有当 count 变化时,useMemo
才会执行。
再次修改 useMemo, 以下:
const double = useMemo(() => {
return count * 2
}, [count === 3])
复制代码
如今能判定,count
在等于 3 以前,因为这个条件一直保存 false
不变,double 不会从新计算,因此一直是 0,当 count
等于 3,double
从新计算为 6,当 count
大于 3,double
在从新计算,变成 8,而后就一直保存 8 不变。
记住,传入的 useMemo
的函数会在渲染期间执行,请不要在这个函数内部执行与渲染无关的放任,诸如反作用这类操做属于 useEffect
的适用范畴,而不是 useMemo
。
你能够把 useMemo 做为性能优化的手段,但不要把它当成语义上的保证。
接下先看一下使用 memo
优化子组件的例子。
const Foo = memo (function Foo (props) {
console.log('Counter render')
return (
<h1>{props.count}</h1>
)
})
function App (props) {
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2
}, [count === 3])
return (
<div style={{padding:'100px'}}>
<button type="button"
onClick={() => {setCount(count + 1) }}
>
Click({count}) double: ({double})
</button>
<Foo count={double}/>
</div>
)
}
复制代码
使用 memo
包裹 Foo
组件,这样只有当 double
变化时,Foo
组件才会从新渲染,执行里面的 log,运行结果以下:
如今在给 Foo
中的 h1
添加一个 click
事件:
const Foo = memo (function Foo (props) {
console.log('Counter render')
return (
<h1 onClick={props.onClick}>{props.count}</h1>
)
})
复制代码
而后在 App 组件中声明 onClick 并传给 Foo
组件:
function App (props) {
...
const onClick = () => {
console.log('Click')
}
return (
<div style={{padding:'100px'}}>
...
<Foo count={double} onClick={onClick}/>
</div>
)
}
复制代码
看下运行效果:
能够看出,每次点击,无论 double
是否有变化, Foo
组件都会被渲染。那就说明每次 App 从新渲染以后, onClick
句柄的变化,致使 Foo
也被连带从新渲染了。count
常常变化能够理解,可是 onClick
就不该该常常变化了,毕竟只是一个函数而已,因此咱们要想办法让 onClick
句柄不变化。
想一想咱们上面讲的 useMemo
,能够这样来优化 onClick
:
const onClick = useMemo(() => {
return () => {
console.log('Click')
}
}, [])
复制代码
因为咱们传给 useMemo
的第二个参数是一个空数组,那么整个逻辑就只会运行一次,理论上咱们返回的 onClick
就只有一个句柄。
运行效果:
如今咱们把 useCallback
来实现上页 useMemo
的逻辑。
const onClick = useCallback(() => {
console.log('Click')
},[])
复制代码
若是 useMemo
返回的是一个函数,那么能够直接使用 useCallback
来省略顶层的函数。
useCallback(fn, deps) 至关于 useMemo(() => fn, deps)
复制代码
你们可能有一个疑问,useCallback
这几行代码明明每次组件渲染都会建立新的函数,它怎么就优化性能了呢。
注意,你们不要误会,使用 useCallback
确实不能阻止建立新的函数,但这个函数不必定会被返回,也就是说这个新建立的函数可能会被抛弃。useCallback
解决的是解决的传入子组件的函数参数过多变化,致使子组件过多渲染的问题,这里须要理解好。
上述咱们第二个参数传入的空数组,在实际业务并无这么简单,至少也要更新一下状态。举个粟子:
function App (props) {
...
const [clickCount, setClickCount] = useState(0);
const onClick = useCallback(() => {
console.log('Click')
setClickCount(clickCount + 1)
},[clickCount, setClickCount])
...
}
复制代码
在 APP 组件中在声明一个 useState
,而后在 onClick
中调用 setClickCount
,此时 onClick 依赖 clickCount
,setClickCount
。
其实这里的 setClickCount
是不须要写的,由于 React 能保证 setState
每次返回的都是同个句柄。不信,能够看下官方文档 :
这里的场景,除了直接使用 setClickCount + 1
赋值之外, 还有一种方式甚至连 clickCount
都不用依赖。setState
除了传入对应的 state
最新值之外,还能够传入一个函数,函数的参数即这个 state
的当前值,返回就是要更新的值:
const onClick = useCallback(() => {
console.log('Click')
setClickCount((clickCount) => clickCount + 1)
},[])
复制代码
和 memo
根据属性来决定是否从新渲染组件同样,useMemo
能够根据指定的依赖不决定一段函数逻辑是否从新执行,从而优化性能。
若是 useMemo
的返回值是函数的话,那么就能够简写成 useCallback
的方式,只是简写而已,实际并无区别。
须要特别注意的是,当依赖变化时,咱们能判定 useMemo
必定从新执行。可是,即便依赖不变化咱们不能假定它就必定不会从新执行,也就是说,它能够会执行,就是考虑内在优化结果。
咱们能够把 useMemo
, useCallback
当作一个锦上添花优化手段,不能够过分依赖它是否从新渲染,由于 React 目前没有打包票说必定执行或者必定不执行。
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。