共计 4884 个字符,预计需要花费 13 分钟才能阅读完成。
极客 Java 进阶训练营完结
超清原画 残缺无密 包含所有视频课件以及源码
点击下崽:网盘链接
入门 React Hooks 及其罕用的几个钩子函数
写在后面
React Hooks 是 React 团队在两年前的 16.8 版本推出的一套全新的机制。作为最支流的前端框架,React 的 API 十分稳固,这次更新的公布,让泛滥恐怖新轮子的前端大佬们虎躯一震,毕竟每一次更新又是高成本的学习,这玩意到底好使么?
答案是好用的,对于 React 的开发者而言,只是多了一个抉择。过来的开发方式是基于 Class 组件的,而 hooks 是基于函数组件,这意味着 这两种开发方式可能并存,而新的代码可能根据具体情况采纳 Hooks 的形式来实现就行了。这篇文章次要就来介绍一下 Hooks 的劣势 和 罕用的几个钩子函数。
Hooks 的劣势
1. 类组件的不足
代码量多:
相较于函数组件的写法,使用类组件代码量要略多一点,这个是最直观的感触。
this 指向:
类组件中总是需要考虑 this 的指向问题,而函数组件则可能忽略。
趋势简单难以保护:
在高版本的 React 中,又更新了一些生命周期函数,因为这些函数互相解耦,很容易造成扩散不集中的写法,漏掉要害逻辑和多了冗余逻辑,导致前期 debug 艰巨。相同,hooks 可能把要害逻辑都放在一起,不显得那么割裂,调试起来也易懂一点。
状态逻辑难复用:
在组件之间复用状态逻辑很难,可能要用到 render props(渲染属性)或者 HOC(高阶组件),但无论是渲染属性,还是高阶组件,都会在原先的组件外包裹一层父容器(一般都是 div 元素),导致层级冗余。
- Hooks 带来的好处
逻辑复用
在组件之前复用状态逻辑,经常需要借助高阶组件等简单的设计模式,这些高级组件会产生冗余的组件节点,让调试变得艰巨,上面用一个 demo 来对比一下两种实现形式。
Class
在 class 组件场景下,定义了一个高阶组件,负责监听窗口大小变动,并将变动后的值作为 props 传给下一个组件。
const useWindowSize = Component => {
// 产生一个高阶组件 HOC,只蕴含监听窗口大小的逻辑
class HOC extends React.PureComponent {
constructor(props) {super(props);
this.state = {size: this.getSize()
};
}
componentDidMount() {window.addEventListener("resize", this.handleResize);
}
componentWillUnmount() {window.removeEventListener("resize", this.handleResize);
}
getSize() {return window.innerWidth > 1000 ? "large":"small";}
handleResize = ()=> {const currentSize = this.getSize();
this.setState({size: this.getSize()
});
}
render() {
// 将窗口大小传送给真正的业务逻辑组件
return ;
}
}
return HOC;
};
复制代码
接下来可能在自定义组件中可能调用 useWindowSize 这样的函数来产生一个新组件,并自带 size 属性,例如:
class MyComponent extends React.Component{
render() {
const {size} = this.props;
if (size === "small") return ;
else return ;
}
}
// 使用 useWindowSize 产生高阶组件,用于产生 size 属性传送给真正的业务组件
export default useWindowSize(MyComponent);
复制代码
上面看下 Hooks 的实现形式
Hooks
const getSize = () => {
return window.innerWidth > 1000 ? “large” : “small”;
}
const useWindowSize = () => {
const [size, setSize] = useState(getSize());
useEffect(() => {
const handler = () => {
setSize(getSize())
};
window.addEventListener('resize', handler);
return () => {window.removeEventListener('resize', handler);
};
}, []);
return size;
};
复制代码
使用:
const Demo = () => {
const size = useWindowSize();
if (size === “small”) return ;
else return ;
};
复制代码
从下面的例子中通过 Hooks 的形式对窗口大小进行了封装,从而将其变成一个可绑定的数据源。这样当窗口大小发生变动时,使用这个 Hook 的组件就都会从新渲染。而且代码也更加简洁和直观,不会产生额定的组件节点,也不显得那么冗余了。
业务代码更加聚合
上面举一个最常见的计时器的例子。
class
let timer = null
componentDidMount() {
timer = setInterval(() => {// ...}, 1000)
}
// …
componentWillUnmount() {
if (timer) clearInterval(timer)
}
复制代码
Hooks
useEffect(() => {
let timer = setInterval(() => {// ...}, 1000)
return () => {if (timer) clearInterval(timer)
}
}, [//…])
复制代码
Hooks 的实现形式能让代码更加集中,逻辑也更清晰。
写法简洁
这个就不举例了,可能从字面意义理解,使用函数组件确实能少些很多代码,懂得都懂,嘻嘻~
几个内置 Hooks 的作用以及使用思考
useState:让函数组件具备维持状态的能力
const[count, setCount]=useState(0);
复制代码
长处:
让函数组件具备维持状态的能力,即:在一个函数组件的多次渲染之间,这个 state 是共享的。便于保护状态。
缺点:
一旦组件有自己状态,意味着组件如果从新创建,就需要有复原状态的过程,这通常会让组件变得更简单。
用法:
useState(initialState) 的参数 initialState 是创建 state 的初始值。
它可能是任意类型,比如数字、对象、数组等等。
useState() 的返回值是一个有着两个元素的数组。第一个数组元素用来读取 state 的值,第二个则是用来设置这个 state 的值。
在这里要注意的是,state 的变量(例子中的 count)是只读的,所以咱们必须通过第二个数组元素 setCount 来设置它的值。
如果要创建多个 state,那么咱们就需要多次调用 useState。
什么样的值应该保存在 state 中?
通常来说,咱们要遵循的一个原则就是:state 中不要保存可能通过计算失去的值。
从 props 传送过去的值。有时候 props 传送过去的值无奈间接使用,而是要通过肯定的计算后再在 UI 上展示,比方说排序。那么咱们要做的就是每次用的时候,都从新排序一下,或者利用某些 cache 机制,而不是将后果间接放到 state 里。
从 URL 中读到的值。比如有时需要读取 URL 中的参数,把它作为组件的一部分状态。那么咱们可能在每次需要用的时候从 URL 中读取,而不是读出来间接放到 state 里。
从 cookie、localStorage 中读取的值。通常来说,也是每次要用的时候间接去读取,而不是读出来后放到 state 里。
useEffect:执行副作用
useEffect(fn, deps);
useEffect,顾名思义,用于执行一段副作用。
什么是副作用?
通常来说,副作用是指一段和以后执行后果无关的代码。比方说要修改函数内部的某个变量,要发动一个请求,等等。
也就是说,在函数组件的当次执行过程中,useEffect 中代码的执行是不影响渲染进去的 UI 的。
对应到 Class 组件,那么 useEffect 就涵盖了 ComponentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法。不过如果你习惯了使用 Class 组件,那千万不要按照把 useEffect 对应到某个或者某几个生命周期的方法。你只需记住,useEffect 是每次组件 render 完后判断依赖并执行就可能了。
useEffect 还有两个非凡的用法:没有依赖项,以及依赖项作为空数组。咱们来具体分析下。
没有依赖项,则每次 render 后都会从新执行。例如:
useEffect(() => {
// 每次 render 完肯定执行
console.log(‘ 渲染 ………..’);
});
复制代码
空数组作为依赖项,则只在首次执行时触发,对应到 Class 组件就是 componentDidMount。例如:
useEffect(() => {
// 组件首次渲染时执行,等价于 class 组件中的 componentDidMount
console.log(‘did mount……..’);
}, []);
复制代码
小结用法:
总结一下,useEffect 让咱们能够在上面四种时机去执行一个回调函数产生副作用:
每次 render 后执行:不提供第二个依赖项参数。
比如 useEffect(() => {})。
仅第一次 render 后执行:提供一个空数组作为依赖项。
比如 useEffect(() => {}, [])。
第一次以及依赖项发生变动后执行:提供依赖项数组。
比如 useEffect(() => {}, [deps])。
组件 unmount 后执行:返回一个回调函数。
比如 useEffect() => { return () => {}}, [])。
useCallback:缓存回调函数
useCallback(fn, deps)
为什么要使用 useCallback?
在 React 函数组件中,每一次 UI 的变动,都是通过从新执行整个函数来实现的,这和传统的 Class 组件有很大区别:函数组件中并没有一个间接的形式在多次渲染之间维持一个状态。
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount(count+1);
return +
}
复制代码
思考下这个过程。每次组件状态发生变动的时候,函数组件实际上都会从新执行一遍。在每次执行的时候,实际上都会创建一个新的事件处理函数 handleIncrement。
这也意味着,即使 count 没有发生变动,然而函数组件因为其它状态发生变动而从新渲染时(函数组件从新被执行),这种写法也会每次创建一个新的函数。创建一个新的事件处理函数,诚然不影响后果的正确性,但其实是没必要的。因为这样做不只减少了零碎的开销,更重要的是:每次创建新函数的形式会让接收事件处理函数的组件,需要从新渲染。
比如这个例子中的 button 组件,接收了 handleIncrement,并作为一个属性。如果每次都是一个新的,那么这个 React 就会认为这个组件的 props 发生了变动,从而必须从新渲染。因此,咱们需要做到的是:只有当 count 发生变动时,咱们才需要从新定一个回调函数。而这正是 useCallback 这个 Hook 的作用。
import React, {useState, useCallback} from ‘react’;
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = useCallback(
() => setCount(count + 1),
[count], // 只有当 count 发生变动时,才会从新创建回调函数
);
return +
}