- 首先 - hook 是React.js在16.8之后推出的一个新特性,它可以让你在不编写class组件下使用state及react其他的特性。
- useState - 第一个简单的 Hook,话不多说直接上代码。
import React, { useState } from 'react'; function Example() { // 声明一个新的叫做 “count” 的 state 变量,如果想改变这个count值,只需要调用setCount即可 const [count, setCount] = useState(0); return ( <div> <p>You 点击 {count} 加一 </p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
原理:单个state值时。
let memoizedState; // 首先是在外部定义了一个 state变量值。 function useState (initialState) { memoizedState = memoizedState || initialState function setState (newState) { memoizedState = newState render() } return [memoizedState, setState] }
多个state时,这时候就需要优化我们的 Hooks ,解决多个 useState 同时使用的问题,当多个状态存在的时候,我们需要使用数组保存状态。
let memoizedStates = [] let index = 0 function useState (initialState) { memoizedStates[index] = memoizedStates[index] || initialState let currentIndex = index function setState (newState) { memoizedStates[currentIndex] = newState render() } return [memoizedStates[index++], setState] }
End: 就是这么简单。
- useEffect: 它是可以让你在函数组件中执行副作用操作,如果你熟悉 React class 的生命周期函数,你可以把
useEffect
当做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合- 常见的副作用( 无需清除,需要清除 )
- 无需清除的 Effect ,有时候我们需要在 React Dom更新之前做一些操作时 ,比如发起网络请求,手动更新dom、记录日志,这些都是常见的无需清除的操作,这些执行之后就可以忽略他们了,class方式,就不必再说,直接上代码
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `点击 ${count} 加一`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> 点击 </button> </div> ); }
这是简单的一个 Effect 使用方法,当然还遇到过这种的。
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState({}); useEffect(() => { setCount({test:"count是一个对象,使得页面死循环"}) },[count]); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> 点击 </button> </div> ); }
执行的结果:死循环
原因:
useEffect接受两个参数:
- 第一个参数是函数(这里叫effect函数),它的作用是,在页面渲染后执行这个函数。因此你可以把ajax请求等放在这里执行;
- 第二个参数是一个数组,这里注意:
参数 现象 注意点 不传 每次渲染后都执行清理或者执行effect 这可能会导致性能问题,比如两次渲染的数据完全一样 空数组 只运行一次的 effect(仅在组件挂载和卸载时执行) 这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行,适用于页面初次加载时进行近执行一次的操作 传 [ count ] React 将对前一次渲染的count和后一次渲染的count进行比较。若相等React 会跳过这个 effect, 实现了性能的优化 所以就上面例子中之所以造成页面的死循环的情况,是因为在JavaScript中,
{} === {}
结果是false,{a:1} === {a:1}
同样,因此造成了react以为两个值不同,就一直的渲染最终页面死循环。需要清除的副作用
当我们组件内,订阅外部文件,定时器 ,全局的某些变量时,组件卸载时,我们需要把这些清理掉。
我们拿一个定时器距离来说
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); // 不想用到外部状态可以用 setCount(count => count + 1); }, 1000); }, [count]); // 确保所有状态依赖都放在这里 console.log(count); return <h1>{count}</h1>; }
这是一个简单的 Effect写法,它的第二个参数,就上面来说是依赖于count变化而执行第一个参数,这样页面就是不断的打印新的count,问题来了,每次更新时,会重新运行 useEffect 的回调函数,也就会重新设置一个定时器。问题又来了,我们上一次设置的定时器并没有清理掉,所以频繁的更新会导致越来越多的定时器同时在运行。 为了解决上面的问题,就需要用到 useEffect 的另一个特性:清除副作用。
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); // 返回一个清理副作用的函数 return () => { clearInterval(id); } }, [count]); console.log(count); return <h1>{count}</h1>; }
这样就好啦 。
- useRef,对于ref 来说,就是在react世界里虽然推崇的虚拟dom,合成事件 ,但是无法避免我们有时候需要找到指定的dom节点时,例如,在react里如果做一张图表时,一般我们是引入图表的UI类库,echarts,antv ,或者canvas时,就需要我们能找到装载图表的div,在16.8之前是又 字符串、回调函数,两种来获取dom节点而Hook, 也是有新的一种方式。
- Ref - 是什么?
const refDom= useRef(initialValue);
- 会返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。
- 返回的 ref 对象在组件的整个生命周期内保持不变
- 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方
- 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里
- useRef 类似于类组件的 this
简单示例
import React, { MutableRefObject, useRef } from 'react'; const TextInputWithFocusButton: React.FC = () => { const inputEl: MutableRefObject<any> = useRef(null) const handleFocus = () => { // `current` 指向已挂载到 DOM 上的文本输入元素 inputEl.current.focus() } return ( <p> <input ref={inputEl} type="text" /> <button onClick={handleFocus}>Focus the input</button> </p> ) } export default TextInputWithFocusButton
通过useRef定义个inputEl变量,在input 元素上定义ref={inputEl},这样通过inputEl.current就可以获取到input Dom 元素,选中则调用下focus函数即可.
更多关于Ref 问题 转载 https://blog.csdn.net/u011705725/article/details/115634265
自定义 Hook
节流函数和防抖函数想必大家都会熟悉,为了让我们在开发中更优雅的使用节流和防抖函数,我们往往需要让某个state也具有节流防抖的功能,或者某个函数的调用,为了避免频繁调用,我们往往也会采取节截流防抖,原生的节流防抖函数可能如一下代码所示:
// 节流 function throttle(func, ms) { let previous = 0; return function() { let now = Date.now(); let context = this; let args = arguments; if (now - previous > ms) { func.apply(context, args); previous = now; } } } // 防抖 function debounce(func, ms) { let timeout; return function () { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args) }, ms); } }
主角来了一下防抖hooks,代码如下:
import { useEffect, useRef } from 'react' const useDebounce = (fn, ms = 30, deps = []) => { let timeout = useRef() useEffect(() => { if (timeout.current) { clearTimeout(timeout.current) } timeout.current = setTimeout(() => { fn() }, ms) }, deps) const cancel = () => { clearTimeout(timeout.current) timeout = null } return [cancel] } export default useDebounce
useDebounce接受三个参数,分别是回调函数,时间间隔以及依赖项数组,它暴露了cancel API,主要是用来控制何时停止防抖函数用的。具体使用如下:
import { useDebounce } from 'hooks' const Home = (props) => { const [a, setA] = useState(0) const [b, setB] = useState(0) const [cancel] = useDebounce(() => { setB(a) }, 2000, [a]) const changeIpt = (e) => { setA(e.target.value) } return <div> <input type="text" onChange={changeIpt} /> { b } { a } </div>
同理,我们继续来实现节流的hooks函数。
import { useEffect, useRef, useState } from 'react' const useThrottle = (fn, ms = 30, deps = []) => { let previous = useRef(0) let [time, setTime] = useState(ms) useEffect(() => { let now = Date.now(); if (now - previous.current > time) { fn(); previous.current = now; } }, deps) const cancel = () => { setTime(0) } return [cancel] } export default useThrottle
代码和自定义useDebounce类似,但需要注意一点就是为了实现cancel功能,我们使用了内部state来处理,通过控制时间间隔来取消节流效果,当然还有很多其他方法可以实现这个 API
End: 当然最开始学习Hook,还是去官网 链接 Hook 简介 – React