React 在 v16.8 的版本中推出了 React Hooks 新特性。在我看来,使用 React Hooks 相比于从前的类组件有以下几点好处:
关于这方面的文章,我们根据使用场景分别进行举例说明,帮助你认识理解并可以熟练运用 React Hooks 大部分特性。辛苦整理良久,还望手动点赞鼓励~
function State(){
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
更新分为以下两种方式,即直接更新和函数式更新,其应用场景的区分点在于:
直接更新不依赖于旧 state 的值;函数式更新依赖于旧 state 的值;
// 直接更新
setState(newCount);
// 函数式更新
setState(prevCount => prevCount - 1);
与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象,而是直接替换它。我们可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。其应用场景在于:创建初始 state 很昂贵时,例如需要通过复杂计算获得;那么则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
function Effect(){
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times`);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
为防止内存泄漏,清除函数会在组件卸载前执行;如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除,即先执行上一个 effect 中 return 的函数,然后再执行本 effect 中非 return 的函数。
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快;(componentDidMount 或 componentDidUpdate 会阻塞浏览器更新屏幕)
默认情况下,React 会每次等待浏览器完成画面渲染之后延迟调用 effect;但是如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:如下所示,如果 count 值两次渲染之间没有发生变化,那么第二次渲染后就会跳过 effect 的调用;
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
如果想只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([ ])作为第二个参数,如下所示,原理跟第 4 点性能优化讲述的一样;
useEffect(() => {
.....
}, []);
要记住 effect 外部的函数使用了哪些 props 和 state 很难,这也是为什么 通常你会想要在 effect 内部 去声明它所需要的函数。
// bad,不推荐
function Example({ someProp }) {
function doSomething() {
console.log(someProp);
}
useEffect(() => {
doSomething();
}, []); // 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}
// good,推荐
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)
}
如果处于某些原因你无法把一个函数移动到 effect 内部,还有一些其他办法:
你可以尝试把那个函数移动到你的组件之外。那样一来,这个函数就肯定不会依赖任何 props 或 state,并且也不用出现在依赖列表中了;万不得已的情况下,你可以 把函数加入 effect 的依赖但 把它的定义包裹 进 useCallback Hook。这就确保了它不随渲染而改变,除非它自身的依赖发生了改变;
推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则,此规则会在添加错误依赖时发出警告并给出修复建议 ;
npm i eslint-plugin-react-hooks --save-dev
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
用来处理多层级传递数据的方式,在以前组件树中,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,我们还可以使用 React Context API 来帮我们做这件事。使用例子如下所示 (1)使用 React Context API,在组件外部建立一个 Context
import React from 'react';
const ThemeContext = React.createContext(0);
export default ThemeContext;
(2)使用 Context.Provider提供了一个 Context 对象,这个对象可以被子组件共享
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import ContextComponent1 from './ContextComponent1';
function ContextPage () {
const [count, setCount] = useState(1);
return (
<div className="App">
<ThemeContext.Provider value={count}>
<ContextComponent1 />
</ThemeContext.Provider>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default ContextPage;
(3)useContext()钩子函数用来引入 Context 对象,并且获取到它的值 // 子组件,在子组件中使用孙组件
import React from 'react';
import ContextComponent2 from './ContextComponent2';
function ContextComponent () {
return (
<ContextComponent2 />
);
}
export default ContextComponent;
// 孙组件,在孙组件中使用 Context 对象值
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ContextComponent () {
const value = useContext(ThemeContext);
return (
<div>useContext:{value}</div>
);
}
export default ContextComponent;
比 useState 更适用的场景:例如 state 逻辑处理较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等;例子如下所示
import React, { useReducer } from 'react';
interface stateType {
count: number
}
interface actionType {
type: string
}
const initialState = { count: 0 };
const reducer = (state:stateType, action:actionType) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
};
const UseReducer = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="App">
<div>useReducer Count:{state.count}</div>
<button onClick={() => { dispatch({ type: 'decrement' }); }}>useReducer 减少</button>
<button onClick={() => { dispatch({ type: 'increment' }); }}>useReducer 增加</button>
</div>
);
};
export default UseReducer;
interface stateType {
count: number
}
interface actionType {
type: string,
paylod?: number
}
const initCount =0
const init = (initCount:number)=>{
return {count:initCount}
}
const reducer = (state:stateType, action:actionType)=>{
switch(action.type){
case 'increment':
return {count: state.count + 1}
case 'decrement':
return {count: state.count - 1}
case 'reset':
return init(action.paylod || 0)
default:
throw new Error();
}
}
const UseReducer = () => {
const [state, dispatch] = useReducer(reducer,initCount,init)
return (
<div className="App">
<div>useReducer Count:{state.count}</div>
<button onClick={()=>{dispatch({type:'decrement'})}}>useReducer 减少</button>
<button onClick={()=>{dispatch({type:'increment'})}}>useReducer 增加</button>
<button onClick={()=>{dispatch({type:'reset',paylod:10 })}}>useReducer 增加</button>
</div>
);
}
export default UseReducer;
如下所示,当父组件重新渲染时,子组件也会重新渲染,即使子组件的 props 和 state 都没有改变
import React, { memo, useState } from 'react';
// 子组件
const ChildComp = () => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<ChildComp/>
</div>
);
};
改进:我们可以使用 memo 包一层,就能解决上面的问题;但是仅仅解决父组件没有传参给子组件的情况以及父组件传简单类型的参数给子组件的情况(例如 string、number、boolean等);如果有传复杂属性应该使用 useCallback(回调事件)或者 useMemo(复杂属性)
// 子组件
const ChildComp = () => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
假设以下场景,父组件在调用子组件时传递 info 对象属性,点击父组件按钮时,发现控制台会打印出子组件被渲染的信息。
import React, { memo, useState } from 'react';
// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = { name, age };
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
点击父组件按钮,触发父组件重新渲染;父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。
使用 useMemo 将对象属性包一层,useMemo 有两个参数:
import React, { memo, useMemo, useState } from 'react';
// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
// 使用 useMemo 将对象属性包一层
const info = useMemo(() => ({ name, age }), [name, age]);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
接着第六章节的例子,假设需要将事件传给子组件,如下所示,当点击父组件按钮时,发现控制台会打印出子组件被渲染的信息,说明子组件又被重新渲染了。
import React, { memo, useMemo, useState } from 'react';
// 子组件
const ChildComp = (props:any) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = useMemo(() => ({ name, age }), [name, age]);
const changeName = () => {
console.log('输出名称...');
};
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info} changeName={changeName}/>
</div>
);
};
export default Parent;
点击父组件按钮,改变了父组件中 count 变量值(父组件的 state 值),进而导致父组件重新渲染;父组件重新渲染时,会重新创建 changeName 函数,即传给子组件的 changeName 属性发生了变化,导致子组件渲染;
修改父组件的 changeName 方法,用 useCallback 钩子函数包裹一层, useCallback 参数与 useMemo 类似
import React, { memo, useCallback, useMemo, useState } from 'react';
// 子组件
const ChildComp = (props:any) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = useMemo(() => ({ name, age }), [name, age]);
const changeName = useCallback(() => {
console.log('输出名称...');
}, []);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info} changeName={changeName}/>
</div>
);
};
export default Parent;
以下分别介绍 useRef 的两个使用场景:
如下所示,使用 useRef 创建的变量指向一个 input 元素,并在页面渲染后使 input 聚焦
import React, { useRef, useEffect } from 'react';
const Page1 = () => {
const myRef = useRef<HTMLInputElement>(null);
useEffect(() => {
myRef?.current?.focus();
});
return (
<div>
<span>UseRef:</span>
<input ref={myRef} type="text"/>
</div>
);
};
export default Page1;
useRef 在 react hook 中的作用, 正如官网说的, 它像一个变量, 类似于 this , 它就像一个盒子, 你可以存放任何东西. createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用,如下例子所示:
import React, { useRef, useEffect, useState } from 'react';
const Page1 = () => {
const myRef2 = useRef(0);
const [count, setCount] = useState(0)
useEffect(()=>{
myRef2.current = count;
});
function handleClick(){
setTimeout(()=>{
console.log(count); // 3
console.log(myRef2.current); // 6
},3000)
}
return (
<div>
<div onClick={()=> setCount(count+1)}>点击count</div>
<div onClick={()=> handleClick()}>查看</div>
</div>
);
}
export default Page1;
使用场景:通过 ref 获取到的是整个 dom 节点,通过 useImperativeHandle 可以控制只暴露一部分方法和属性,而不是整个 dom 节点。
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect,这里不再举例。
useLayoutEffect 和平常写的 Class 组件的 componentDidMount 和 componentDidUpdate 同时执行;
useEffect 会在本次更新完成后,也就是第 1 点的方法执行完成后,再开启一次任务调度,在下次任务调度中执行 useEffect;
关于这方面的文章,我们根据使用场景分别进行举例说明,希望有帮助到你认识理解并可以熟练运用 React Hooks 大部分特性。
辛苦整理良久,还望手动点赞鼓励~
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/N5ipkCpWJigW1jgvp5MS6w
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。