主要从3个方面研究React Hookjavascript
根据黄金思惟圈(What、How、Why)java
什么是Hook?react
打开Google翻译,获得的解释:钩、钩子ios
再看看React官网的解释:They let you use state and other React features without writing a class.(它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。)ajax
因此,结合一下。我我的的理解是这样的:对于函数式的组件,能够用钩子(Hook)将想要的外部功能给“钩”进来。redux
在React Hook出来以前,函数式组件都是无状态的组件,最多就是根据props
来加一些判断的逻辑;而在React Hook出来以后就能够在函数式组件里面加入状态(useState),类生命周期(useEffect),甚至是一些本身的复用逻辑(自定义Hook)等等这些外部的功能。axios
怎么使用Hook?api
你们一块儿看一下官网的一个例子。数组
题目:显示一个计数器。当你点击按钮,计数器的值就会增长。浏览器
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div>
);
}
}
复制代码
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量 const [count, setCount] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
复制代码
这样就算是完成了一个最简单的React Hook 实践,关于一些官方提供的Hook晚点会介绍。
作任何一件事情我以为都应该理清这个两个问题,这样的话就会事半功倍。
咱们先看看React官方是怎么解释“Why”的
- 在组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的 class
本人我的认为第三点是来凑数的....
为何这么说?
由于React用了这么久了基本都是在使用Class组件,这个是在以前,哪怕是如今学习React的必经之路吧!因此,这点我接下来就会跳过了😂
其实高阶组件或者说是props都是很好的解决了复杂的聚合业务逻辑,那为何说在组件之间服用状态逻辑很难呢?
其实道理很是简单。
举个简单的例子,方便你们理解。
场景:有 请求A,请求B,请求C,请求D。他们的请求都有相互依赖关系好比,发请求B的时候必须拿到请求A的结果中的某个值,而请求C也必须拿到请求B的结果中的某个值。以此类推请求D。
Promise出来以前是怎么作的呢?
$.ajax({
type:"post",
success: function(){//成功回调
//再次异步请求
$.ajax({
type:"post",
url:"...",
success:function(){//成功回调
//再次异步请求
$.ajax({
type:"post",
url:"...",
success:function(){
.......//如此循环
}
})
}
})
}
})
复制代码
这还只是3层,若是是100层呢?那看起来就很是的难受了!
Promise较好的解决了这个问题
new Promise(f1)
.then(f2)
.then(f3)
.then(f4)
.then(f5)
.then(f5)
…………
复制代码
而后是async/await。这里就不展开了,有兴趣的能够本身去了解一下。
结论
之因此这么大费周章的讲是为了解释,React中的高阶组件(HOC)。他的逻辑其实和回调地狱相似,一个两个其实都还算优雅或者说舒服,一旦多了的话。。。
export default withHover(
withTheme(
withAuth(
withRepos(Profile)
)
)
)
// 就会变成这样,不够优雅
<WithHover>
<WithTheme hovering={false}>
<WithAuth hovering={false} theme='dark'>
<WithRepos hovering={false} theme='dark' authed={true}>
<Profile
id='JavaScript'
loading={true}
repos={[]}
authed={true}
theme='dark'
hovering={false}
/>
</WithRepos>
</WithAuth>
<WithTheme>
</WithHover>
复制代码
并且每一个高阶组件的逻辑复用咱们可能还要一个个去研读。
其实,这点很是好理解。举一个很是简单常见的例子你们就会明白了。
场景:假如我有一个子组件Child,他的功能是这样的:父组建会给一个id,在组件建立的时候获取一下有关信息,在id改变的时候再从新获取。
componentDidMount () {
this.fetch(this.props.id)
}
componentDidUpdate (prevProps) {
if (prevProps.id !== this.props.id) {
this.fetch(this.props.id)
}
}
fetch = id => {
this.setState({ loading: true })
fetchInfo(id)
.then(info => this.setState({
info,
loading: false
}))
}
复制代码
const fetch = id => {
this.setState({ loading: true })
fetchInfo(id)
.then(info => this.setState({
info,
loading: false
}))
}
useEffect(() => {
fetch(this.props.id)
}, [this.props.id])
复制代码
简单的说一下他的优势吧。
简单的使用在上面的探究-How里面有介绍,更多的在React官网也有介绍。
function Counter() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> <button onClick={handleAlertClick}> Show alert </button> </div>
);
}
复制代码
你猜alert会弹出什么呢?会是5吗?— 这个值是alert的时候counter的实时状态。或者会是3吗?— 这个值是我点击时候的状态。
分割线
来本身 试试吧!
3
这是为何呢?function组建到底是若是工做的呢?
咱们发现count
在每一次函数调用中都是一个常量值。值得强调的是 — 咱们的组件函数每次渲染都会被调用,可是每一次调用中count
值都是常量,而且它被赋予了当前渲染中的状态值。
这并非React特有的,普通的函数也有相似的行为:
function sayHi(person) {
const name = person.name; setTimeout(() => {
alert('Hello, ' + name);
}, 3000);
}
let someone = {name: 'Dan'};
sayHi(someone);
someone = {name: 'Yuzhi'};
sayHi(someone);
someone = {name: 'Dominic'};
sayHi(someone);
复制代码
在 这个例子中, 外层的someone
会被赋值不少次(就像在React中,当前的组件状态会改变同样)。**而后,在sayHi
函数中,局部常量name
会和某次调用中的person
关联。**由于这个常量是局部的,因此每一次调用都是相互独立的。结果就是,当定时器回调触发的时候,每个alert都会弹出它拥有的name
。
这就解释了咱们的事件处理函数如何捕获了点击时候的count
值。若是咱们应用相同的替换原理,每一次渲染“看到”的是它本身的count
:
// During first render
function Counter() {
const count = 0; // Returned by useState() // ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
// ...
}
// After a click, our function is called again
function Counter() {
const count = 1; // Returned by useState() // ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
// ...
}
// After another click, our function is called again
function Counter() {
const count = 2; // Returned by useState() // ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
// ...
}
复制代码
因此实际上,每一次渲染都有一个“新版本”的handleAlertClick
。每个版本的handleAlertClick
“记住” 了它本身的 count
:
// During first render
function Counter() {
// ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + 0); }, 3000);
}
// ...
<button onClick={handleAlertClick} /> // The one with 0 inside // ...
}
// After a click, our function is called again
function Counter() {
// ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + 1); }, 3000);
}
// ...
<button onClick={handleAlertClick} /> // The one with 1 inside // ...
}
// After another click, our function is called again
function Counter() {
// ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + 2); }, 3000);
}
// ...
<button onClick={handleAlertClick} /> // The one with 2 inside // ...
}
复制代码
这就是为何在这个demo中中,事件处理函数“属于”某一次特定的渲染,当你点击的时候,它会使用那次渲染中counter
的状态值。
**在任意一次渲染中,props和state是始终保持不变的。**若是props和state在不一样的渲染中是相互独立的,那么使用到它们的任何值也是独立的(包括事件处理函数)。它们都“属于”一次特定的渲染。即使是事件处理中的异步函数调用“看到”的也是此次渲染中的count
值。
function Counter() {
const [count, setCount] = useState(0);
const addCount = () => {
setCount(count+1)
setCount(count+2)
setCount(count+3)
setCount(count+4)
setCount(count+5)
}
console.log(count)
return (
<div> <button onClick={addCount}> Click me </button> </div>
);
}
复制代码
分割线
5
为何呢?
useState的更新到底是如何工做的呢?
咱们进入ReactHooks.js
来看看,发现useState
的实现居然异常简单,只有短短两行
// ReactHooks.js
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
复制代码
其实能够这样理解useState,useState其实就是useReducer的一个语法糖;可是这个不在这个问题的讨论范围内;
好,收回来。
其实咱们在const [xx, setXx] = useState(xx)
的时候就生成一个队列,咱们暂时叫它为queue;全部这一轮运行读取到的state都被放到一个链表的队列里面去,而后再用do-while循环,每次都是拿到最新的值,可是不是Object.assgin的形式,而是直接赋值。话很少说直接源码。
function updateReducer(reducer, initialArg, init) {
// 获取初始化时的 hook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
// 开始渲染更新
if (numberOfReRenders > 0) {
const dispatch = queue.dispatch;
if (renderPhaseUpdates !== null) {
// 获取Hook对象上的 queue,内部存有本次更新的一系列数据
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = hook.memoizedState;
let update = firstRenderPhaseUpdate;
// 获取更新后的state
do {
const action = update.action;
// 此时的reducer是basicStateReducer,直接返回action的值
// 注意,这里是等于号因此
/** * * setObj({ a: 1, b: 1, c: 1 }) * setObj({ a: 2, b: 2 }) * setObj({ a: 3 }) * * 到最后也只有只有{ a: 3 },而b和c全没了 * **/
newState = reducer(newState, action);
update = update.next;
} while (update !== null);
// 对 更新hook.memoized
hook.memoizedState = newState;
// 返回新的 state,及更新 hook 的 dispatch 方法
return [newState, dispatch];
}
}
}
复制代码
在学习useEffect这个Hook的时候,淡化你知道的“生命周期”这个概念。
执行的时机不一样
那么具体哪里不一样呢?
其实在初始化useEffect和useLayoutEffect是没有区别的,他们真正的区别在于初始化以后;
举个很是形象的例子🌰:
除了初始化以后的一轮更新:
浏览器:我要绘制了!
React:等等,我有一个哥们临时有事要处理,他是:useLayoutEffect
useLayoutEffect执行....
React:好了,你能够开始绘制了~@浏览器
浏览器:好的
浏览器更新UI...
浏览器:我更新好了。你有什么事要作的吗?@React
React:有的,useEffect你上
useEffect执行....
可能有点废话了。其实区别就是
useLayoutEffect()
浏览器绘制
useEffect()
这样其实你们也能很直接的看到弊端了。那就是useLayoutEffect若是有大量的计算的话,那样可能会阻塞UI更新,或者说UI渲染。因此仍是要谨慎使用。
通常来讲他们没有什么太大的区别的,若是真的要使用useLayoutEffect的话要谨慎一些。否则可能会致使UI渲染阻塞之类的问题。
可是,也不是没有使用场景。
好比下面的这个代码就很须要useLayoutEffect
function App() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
if (count === 0) {
const randomNum = 10 + Math.random()*200
setCount(10 + Math.random()*200);
}
}, [count]);
return (
<div onClick={() => setCount(0)}>{count}</div>
);
}
// 我是分割线
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count === 0) {
const randomNum = 10 + Math.random()*200
setCount(10 + Math.random()*200);
}
}, [count]);
return (
<div onClick={() => setCount(0)}>{count}</div>
);
}
复制代码
其实明白的同窗一下就看出来了,若是使用useEffect的话会出现闪烁,会先回到0而后再更新新的随机数。而反观useLayoutEffect则不会,他会很天然的过渡。
总结:
useLayoutEffect的使用场景为:有一个中间状态但愿隐藏的时候再使用。
大部分状况下useEffect能够适用于99%的场景。
function SearchResults() {
const [query, setQuery] = useState('react');
// Imagine this function is also long
function getFetchUrl() {
return 'https://hn.algolia.com/api/v1/search?query=' + query;
}
// Imagine this function is also long
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
}
useEffect(() => {
fetchData();
}, []);
// ...
}
复制代码
不难看出上面代码的意思是。想要模仿componentDidMount的生命周期,在页面或者组件加载以后发送一个请求。咋一看好像没有什么问题(实际在运行的过程当中也没有什么问题,在写这篇文章以前我也是这么作的。)
可是你们能够想象一下,若是这个函数组件,是如今的5倍大,这个didMount里面调用的请求,将来依赖的东西你均可以100%的察觉到吗?
我以为难!不免会有疏忽。到时候可能就会出现state或者props读取错误的状况。由于每一次render的state和props都是独立的。
那么,该如何解决呢?
有一个很土的办法,直接把函数扔到useEffect里面去
function SearchResults() {
// ...
useEffect(() => {
// We moved these functions inside!
function getFetchUrl() {
return 'https://hn.algolia.com/api/v1/search?query=react';
}
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
}
fetchData();
}, []); // ✅ Deps are OK
// ...
}
复制代码
那高级点的办法呢?
function SearchResults() {
// ✅ Preserves identity when its own deps are the same
const getFetchUrl = useCallback((query) => {
return 'https://hn.algolia.com/api/v1/search?query=' + query;
}, []); // ✅ Callback deps are OK
useEffect(() => {
const url = getFetchUrl('react');
// ... Fetch data and do something ...
}, [getFetchUrl]); // ✅ Effect deps are OK
// ...
}
复制代码
将函数用useCallback包裹,这样的话咱们只须要作useEffect的依赖里面写上咱们的函数,而后在useCallback里面写上咱们的依赖。
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
复制代码
其实很好理解,若是看了前面useEffect和useLayoutEffect区别的同窗一下就能够知道这个的实现原理。
首先,在一切都更新以后,而后会会执行useEffect内部的回调函数,将prevCount给赋值,因为没有触发渲染,因此只是单纯的赋值。这样就看起来prevCount的值永远都慢一步。
其实在学习useEffect的时候。应该忘记你对React的一些知识。好比生命周期,在函数组件里面没有生命周期这个概念了。
每一次的render他都有本身的state和props。state和props更应该被看做一个常量,哪怕是const bar = xx这样的常量。这样理解起来useEffect这个反作用其实会更加顺畅,也不容易进入他的“陷阱”
为何我把useRef单独拎出来讲,不把他和useImperativeHandle
放在一块儿讲,由于
(官网原话)它建立的是一个普通 Javascript 对象。而
useRef()
和自建一个{current: ...}
对象的惟一区别是,useRef
会在每次渲染时返回同一个 ref 对象。
记住useRef
不仅仅用于获取DOM节点和组件实例
,还有一个巧妙的用法就是做为容器保留可变变量
,能够这样说:没法自如地使用useRef会让你失去hook将近一半的能力
useRef
仅能用在 FunctionComponent,createRef
仅能用在 ClassComponent。
useRef
仅能用在 FunctionComponent,createRef
仅能用在 ClassComponent。
第一句话是显然的,由于 Hooks 不能用在 ClassComponent。
第二句话的缘由是,createRef
并无 Hooks 的效果,其值会随着 FunctionComponent 重复执行而不断被初始化:
function App() {
// 错误用法,永远也拿不到 ref
const valueRef = React.createRef();
return <div ref={valueRef} />;
}
复制代码
复制代码
上述 valueRef
会随着 App 函数的 Render 而重复初始化,这也是 Hooks 的独特之处,虽然用在普通函数中,但在 React 引擎中会获得超出普通函数的表现,好比初始化仅执行一次,或者引用不变。
为何 createRef
能够在 ClassComponent 正常运行呢?这是由于 ClassComponent 分离了生命周期,使例如 componentDidMount
等初始化时机仅执行一次。
首先,题目怎么理解?题目
若是咱们但愿他alert的时候能够获取到最新的值的话,可使用useRef来解决
const Counter = () => {
const [count, setCount] = useState<number>(0)
const countRef = useRef<number>(count)
useEffect(() => {
countRef.current = count
})
const handleCount = () => {
setTimeout(() => {
alert('current count: ' + countRef.current)
}, 3000);
}
//...
}
export default Counter
复制代码
先来看看memo没有回调函数的时候他作了什么。
memo它是一个高阶组件(HOC)他与React.PureComponent十分类似。除了使用的地方不一样(Class组件和Function组件)以外几乎一致。
Memo内部和PureComponent同样使用Object.is用于前对比,若是传入的props内存地址不变的话,那就不会渲染了(或者说复用最近的一次渲染)。
下面能够看源码事例
function updateMemoComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps: any, updateExpirationTime, renderExpirationTime: ExpirationTime, ): null | Fiber {
/* ...省略...*/
// 判断更新的过时时间是否小于渲染的过时时间
if (updateExpirationTime < renderExpirationTime) {
const prevProps = currentChild.memoizedProps;
// 若是自定义了compare函数,则采用自定义的compare函数,不然采用官方的shallowEqual(浅比较)函数。(下面有解析)
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
/** * 1. 判断当前 props 与 nextProps 是否相等; * 2. 判断即将渲染组件的引用是否与workInProgress Fiber中的引用是否一致; * * 只有二者都为真,才会退出渲染。 */
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
// 若是都为真,则退出渲染
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
/* ...省略...*/
复制代码
shallowEqual(浅比较)
// 用原型链的方法
const hasOwn = Object.prototype.hasOwnProperty
// 这个函数其实是Object.is()的polyfill
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
export default function shallowEqual(objA, objB) {
// 首先对基本数据类型的比较
if (is(objA, objB)) return true
// 因为Obejct.is()能够对基本数据类型作一个精确的比较, 因此若是不等
// 只有一种状况是误判的,那就是object,因此在判断两个对象都不是object
// 以后,就能够返回false了
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false
}
// 过滤掉基本数据类型以后,就是对对象的比较了
// 首先拿出key值,对key的长度进行对比
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
// 长度不等直接返回false
if (keysA.length !== keysB.length) return false
// key相等的状况下,在去循环比较
for (let i = 0; i < keysA.length; i++) {
// key值相等的时候
// 借用原型链上真正的 hasOwnProperty 方法,判断ObjB里面是否有A的key的key值
// 属性的顺序不影响结果也就是{name:'daisy', age:'24'} 跟{age:'24',name:'daisy' }是同样的
// 最后,对对象的value进行一个基本数据类型的比较,返回结果
if (!hasOwn.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return truea
}
复制代码
由源码能够知道,加入没有传一个比较的回调函数会使用官方的浅比较。具体的能够看注释
咱们都知道react的生命周期中有一个shouldComponentUpdate。在这个函数中返回true的话就表明本次render须要执行,而返回false就能够跳过本次的render。
而memo正好相反,返回true表示本次跳过,返回false就表示本次须要执行render。
具体怎么用呢?
你们能够本身运行一下,看看效果。必定要本身试一下,否则很容易和shouldComponentUpdate弄混了。学习仍是要本身动手才行。
function ChangeLog({w = ''}){
// 省略
console.log('====render====')
}
export default memo(ChangeLog, (prevProps, nextProps) => {
if (prevProps.w !== nextProps.w) {
return false
}
return true
});
复制代码
之因此把这useMomo/useCallack两兄弟和在一块儿。是由于他们其实十分类似。
一个是缓存变量(useMemo),一个是缓存函数(useCallback)。
其实这么一说就清晰不少了。
具体的使用和useEffect同样,都是第一个参数为调用时的回调函数,第二个参数是调用判断所监听的值(能够是变量,也能够是函数)
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedValue = useCallback(() => computeExpensiveValue(a, b), [a, b]);
复制代码
useMemo() 返回的是一个 memoized 值,只有当依赖项(好比上面的 a,b 发生变化的时候,才会从新计算这个 memoized 值)
memoized 值不变的状况下,不会从新触发渲染逻辑。
提及渲染逻辑,须要记住的是 useMemo() 是在 render 期间执行的,因此不能进行一些额外的副操做,好比网络请求等。
若是没有提供依赖数组(上面的 [a,b])则每次都会从新计算 memoized 值,也就会 re-redner
useCallback也是同样的,这里就很少赘述了。
帮他们两兄弟和在一块儿说主要说由于他们两兄弟在通常状况下是能够与Redux一战的。
可是!!
可是啊,可是若是你须要中间价,或者说须要“时间旅行”,又或者临时须要跨页面级的数据共享,那你仍是须要redux来解决的。不过基本上的场景咱们使用useReducer和useContext就能够完美的替代redux了。
其实以前因为要起一个新项目,可是忽然发现有一个爷爷组件的值为须要通知给孙子组件,而后孙子组件可能会用掉回调函数调用爷爷组件的方法。那个时候其实已经用Hook写了一半了,懒得加Redux了,又不想一层层传props下去。怎么办?
经过了解,我知道了useReducer/useContext刚恰好能够解决个人需求。
const TodosDispatch = React.createContext(null);
const initialState = { bar: null };
function reducer(state, action) {
switch (action.type) {
case 'setCenter':
return { ...state, bar: action.bar };
default:
return state
}
}
// 虽然这个是自组件,可是哪怕是曾曾曾孙子组件均可以直接用useContext拿到dispatch
function DeepChild(props) {
// 若是咱们想要执行一个 action,咱们能够从 context 中获取 dispatch。
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
function TodosApp() {
// 提示:`dispatch` 不会在从新渲染之间变化
const [state, dispatch] = useReducer(reducer, initialState);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
复制代码
这里就很少说了,基本上没有什么坑点和疑难点。
说一下基本用法和ant-design form中的使用
useImperativeHandle(ref, createHandle, [deps])
复制代码
useImperativeHandle
可让你在使用 ref
时自定义暴露给父组件的实例值。在大多数状况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 forwardRef
一块儿使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput); 复制代码
在本例中,渲染的父组件能够调用 inputRef.current.focus()
。
// ref从第二个参数取,这里都是一致的
const Example = (props,ref) => {
const bar = () => {}
useImperativeHandle(ref, () => ({
text: bar,
}));
return (
<> <Form> {//....} </Form> </> ); }; export default memo(Form.create()(forwardRef(Example))); 复制代码
const Parent = () => {
return (
<>
<Example
// 注意这个再也不是传ref了,而是传wrappedComponentRef。由于antd的form他返回的是一个新的对象,这个是他自定义的一个接收ref的值
wrappedComponentRef={editTemplateRef}
/>
</>
);
};
复制代码
useDebugValue是专门用于服务自定义的Hook的。
具体看看使用就好
useDebugValue,目的是能在react的浏览器调试工具上显示你的自定义hooks,或者给hooks标记一些东西 当使用一个参数的时候,就是把第一个参数标记在react的调试工具上,下面写一个简单的例子
import React, { useDebugValue, useState } from 'react';
const useTest = () => {
const [str, setStr] = useState<string>('');
useDebugValue('debug');
return {
str, setStr
}
}
export default (): JSX.Element => {
const { str, setStr } = useTest();
return (
<> <h2>{str}</h2> <button onClick={() => { setStr('从新渲染'); }}>这是???</button> </> ); } 复制代码
会在自定义的hooks标记到react的调试工具上面,主要用于调试工具调试使用
当传入第二个参数的状况下,第二个参数是一个回调函数,会把第一个参数当成本身的形参传入,进行一系列的操做,return回去,而后才会在react调试工具的hooks中打印出来,否则不会显示
import React, { useDebugValue, useState } from 'react';
const useTest = () => {
const [str, setStr] = useState<string>('');
useDebugValue(str, (value:string) => {
console.log(value);
return '这是改造后的' + value;
});
return {
str, setStr
}
}
export default (): JSX.Element => {
const { str, setStr } = useTest();
return (
<> <h2>{str}</h2> <button onClick={() => { setStr('从新渲染'); }}>这是???</button> </> ); } 复制代码
结果:
同时在控制台上打印了一个空字符
因为str的初始值是空的,因此打印就是空的了,这只是调试使用,hooks差很少就这些了,没有其余的了
若是有哪里写的不对或者有疑问的欢迎你们在评论区互动。