当我们讨论 hooks 时到底在讨论什么

async getData() {

// 这是一段异步请求数据的代码

console.log(数据请求了,参数为:${this.props.query});

this.setState({

keyword: this.props.query

})

}

render() {

return (

关键词: {this.state.keyword}

);

}

}

而在应用了 Hooks 的函数组件中,我们思考“做什么”:不同 query 值,展示不同的数据。

function Detail({

query

}) {

const [keyword, setKeyword] = useState(‘’);

useEffect(() => {

const getData = async () => {

console.log(数据请求了,参数为:${query});

setKeyword(query);

}

getData();

}, [query]);

return (

关键词: {keyword}

);

}

在这种主导下,开发者在编码过程中的思维模式也应随之改变,需要考虑数据与数据、数据与行为之间的同步关系。这种模式可以更简洁地将相关代码组合到一起,甚至抽象成自定义 hooks,实现逻辑的共享,似乎有了插拔式编程的味道???。

虽然 Dan Abramov 在自己的博客中提到,从生命周期的角度思考并决定何时执行副作用是在逆势而为[2],但是了解各个 hooks 在组件渲染过程中的执行时机,有助于我们与 React 保持理解的一致性,能够更加准确地专注于“做什么”。Donavon 以图表形式梳理对比了 hooks 范式与生命周期范式[3],能够帮助我们理解 hooks 在组件中的工作机制。每次组件发生更新时,都会重新调用组件函数,生成新的作用域,这种变化也对我们开发者提出了新的编码要求。

hook flow

作用域


在类组件中,组件一旦实例化后,便有了自己的作用域,从创建到销毁,作用域始终不变。因此,在整个组件的生命周期中,每次渲染时内部变量始终指向同一个引用,我们可以很轻易的在每次渲染中通过 this.state 拿到最新的状态值,也可以使用 this.xx 获取到同一个内部变量。

class Timer extends React.Component {

state = {

count: 0,

interval: null,

}

componentDidMount() {

const interval = setInterval(() => {

this.setState({

count: this.state.count + 1,

})

}, 1000);

this.setState({

interval

});

}

componentDidUnMount() {

if (this.state.interval) {

clearInterval(this.state.interval);

}

}

render() {

return (

计数器为:{this.state.count}

);

}

}

Hooks 中, renderstate 的关系更像闭包与局部变量。每次渲染时,都会生成新的 state 变量,React 会向其写入当次渲染的状态值,并在当次渲染过程中保持不变。也即每次渲染互相独立,都有自己的状态值。同理,组件内的函数、定时器、副作用等也是独立的,内部所访问的也是当次渲染的状态值,因此常常会遇到定时器/订阅器内无法读取到最新值的情况。

function Timer() {

const [count, setCount] = useState(0);

useEffect(() => {

const interval = setInterval(() => {

setCount(count + 1);    // 始终只为 1

}, 1000);

return () => {

clearInterval(interval);

}

}, []);

return (

计数器为:{count}

);

}

如果我们想要拿到最新值,有两种解决方法:一是利用 setCount 的 lambada 形式,传入一个以上一次的状态值为参数的函数;二是借助 useRef 钩子,在其 current 属性中存储最新的值。

function Timer() {

const [count, setCount] = useState(0);

useEffect(() => {

const interval = setInterval(() => {

setCount(c => c + 1);

}, 1000);

return () => {

clearInterval(interval);

}

}, []);

return (

计数器为:{count}

);

}

在 hook-flow 的图中,我们可以了解到当父组件发生重新渲染时,其所有(状态、局部变量等)都是新的。一旦子组件依赖于父组件的某一个对象变量,那么无论对象是否发生变化,子组件拿到的都是新的对象,从而使子组件对应的 diff 失效,依旧会重新执行该部分逻辑。在下面的例子中,我们的副作用依赖项中包含了父组件传入的对象参数,每次父组件发生更新时,都会触发数据请求。

function Info({

style,

}) {

console.log(‘Info 发生渲染’);

useEffect(() => {

console.log(‘重新加载数据’); // 每次发生重新渲染时,都会重新加载数据

}, [style]);

return (

这是 Info 里的文字

);

}

function Page() {

console.log(‘Page 发生渲染’);

const [count, setCount] = useState(0);

const style = { color: ‘red’ };

// 计数器 +1 时,引发 Page 的重新渲染,进而引发 Info 的重新渲染

return (

计数值为:{count}

<button onClick={() => setCount(count + 1)}> +1 

);

}

React Hooks 给我们提供了解决方案,useMemo 允许我们缓存传入的对象,仅当依赖项发生变化时,才重新计算并更新相应的对象。

function Page() {

console.log(‘Page 发生渲染’);

const [color] = useState(‘red’);

const [count, setCount] = useState(0);

const style = useMemo(() => ({ color }), [color]); // 只有 color 发生实质性改变时,style 才会变化

// 计数器 +1 时,引发 Page 的重新渲染,进而引发 Info 的重新渲染

// 但是由于 style 缓存了,因此不会触发 Info 内的数据重新加载

return (

计数值为:{count}

<button onClick={() => setCount(count + 1)}> +1 

);

}

数据流


React Hooks 在数据流上带来的变化有两点:一是支持更友好的使用 context 进行状态管理,避免层级过多时向中间层承载无关参数;二是允许函数参与到数据流中,避免向下层组件传入多余的参数。

useContext 作为 hooks 的核心模块之一,可以获取到传入 context 的当前值,以此达到跨层通信的目的。React 官网有着详细的介绍,需要关注的是一旦 context 值发生改变,所有使用了该 context 的组件都会重新渲染。为了避免无关的组件重绘,我们需要合理的构建 context ,比如从第一节提到的新思维模式出发,按状态的相关度组织 context,将相关状态存储在同一个 context 中。

在过去,如果父子组件用到同一个数据请求方法 getData ,而该方法又依赖于上层传入的 query 值时,通常需要将 querygetData 方法一起传递给子组件,子组件通过判断 query 值来决定是否重新执行 getData

class Parent extends React.Component {

state = {

query: ‘keyword’,

}

getData() {

const url = https://mocks.alibaba-inc.com/mock/fO87jdfKqX/demo/queryData.json?query=${this.state.query};

// 请求数据…

console.log(请求路径为:${url});

}

render() {

return (

// 传递了一个子组件不渲染的 query 值

);

}

}

class Child extends React.Component {

componentDidMount() {

this.props.getData();

}

componentDidUpdate(prevProps) {

// if (prevProps.getData !== this.props.getData) { // 该条件始终为 true

//   this.props.getData();

// }

if (prevProps.query !== this.props.query) { // 只能借助 query 值来做判断

this.props.getData();

}

}

render() {

return (

// …

);

}

}

在 React Hooks 中 useCallback 支持我们缓存某一函数,当且仅当依赖项发生变化时,才更新该函数。这使得我们可以在子组件中配合 useEffect ,实现按需加载。通过 hooks 的配合,使得函数不再仅仅是一个方法,而是可以作为一个值参与到应用的数据流中。

function Parent() {

const [count, setCount] = useState(0);

const [query, setQuery] = useState(‘keyword’);

const getData = useCallback(() => {

const url = https://mocks.alibaba-inc.com/mock/fO87jdfKqX/demo/queryData.json?query=${query};

// 请求数据…

console.log(请求路径为:${url});

}, [query]);  // 当且仅当 query 改变时 getData 才更新

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可获取,包括答案解析。

友,同时减轻大家的负担。**

[外链图片转存中…(img-wTWvJiL0-1713268797466)]

[外链图片转存中…(img-42Et05B5-1713268797467)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-j0ahsANS-1713268797467)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可获取,包括答案解析。

[外链图片转存中…(img-3WE7RE0N-1713268797467)]

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值