函数组件监听数据不更新
https://zh-hans.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often
如果我的 effect 的依赖频繁变化,我该怎么办?
https://zhuanlan.zhihu.com/p/85497202
https://www.it610.com/article/1282852463728148480.htm
https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/
一、函数组件真正地把数据和渲染绑定到了一起
函数组件会捕获 render 内部的状态,这是两类组件最大的不同。
初读这篇文章时,不免感慨 JS 闭包机制竟能给到我们这么重要的解决问题的灵感。但在反复思考过后的现在,我们可认知到这样一件事情——类组件和函数组件之间,纵有千差万别,但最不能够被忽视掉的,是心智模式层面的差异,是面向对象和函数式编程这两套不同的设计思想之间的差异。
具体而言,就是函数组件更加契合 React 框架的设计理念。何出此言?不要忘了这个赫赫有名的 React 公式:
image.png
不夸张地说,React 组件本身的定位就是函数,一个吃进数据、吐出 UI 的函数。作为开发者,我们编写的是声明式的代码,而 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。
那为什么类组件做不到?这里摘出上述文章中的 Demo,站在一个新的视角来解读一下“函数组件会捕获 render 内部的状态,这是两类组件最大的不同”这个结论。首先来看这样一个类组件:
/**
import React, {useState} from 'react';
import './index.less';
class ProfilePageCla extends React.Component {
showMessage = () => {
alert('Followed ' + this.props.user);
};
handleClick = () => {
setTimeout(this.showMessage, 3000);
};
render () {
return <button onClick={this.handleClick}>Follow</button>;
}
}
function ProfilePageFun (props) {
const showMessage = () => {
alert('Followed ' + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<button onClick={handleClick}>Follow</button>
);
}
export function Test3 (props) {
const [user, setUser] = useState('Dan');
const [data] = useState([{value: 'Dan', label: 'Dan'}, {value: 'Sophie', label: 'Sophie'}]);
function radioChange (value) {
console.log(value.target.value);
setUser(value.target.value);
}
return (<div style={{'fontSize': '18px'}}>
<p>choose: </p>
{data.map(i => (
<p><input type={'radio'} name={i.value} value={i.value} key={i.value} checked={user === i.value} onChange={radioChange}/>{i.value}</p>
))}
<p>welcome to {user}</p>
<p>class: <ProfilePageCla user={user}/></p>
<p>function: <ProfilePageFun user={user}/></p>
</div>);
}
这个组件返回的是一个按钮,交互内容也很简单:点击按钮后,过 3s,界面上会弹出“Followed xxx”的文案。类似于我们在微博上点击“关注某人”之后弹出的“已关注”这样的提醒。
看起来好像没啥毛病,但是如果在这个在线 Demo中尝试点击基于类组件形式编写的 ProfilePage 按钮后 3s 内把用户切换为 Sophie,你就会看到如下的效果:
class情况下:
明明我们是在 Dan 的主页点击的关注,结果却提示了“Followed Sophie”!
这个现象必然让许多人感到困惑:user 的内容是通过 props 下发的,props 作为不可变值,为什么会从 Dan 变成 Sophie 呢?
因为虽然 props 本身是不可变的,但 this 却是可变的,this 上的数据是可以被修改的,this.props 的调用每次都会获取最新的 props,而这正是 React 确保数据实时性的一个重要手段。
多数情况下,在 React 生命周期对执行顺序的调控下,this.props 和 this.state 的变化都能够和预期中的渲染动作保持一致。但在这个案例中,通过 setTimeout 将预期中的渲染推迟了 3s,打破了 this.props 和渲染动作之间的这种时机上的关联,进而导致渲染时捕获到的是一个错误的、修改后的 this.props。这就是问题的所在。
function情况下:
props 会在 ProfilePage 函数执行的一瞬间就被捕获,而 props 本身又是一个不可变值,因此可以充分确保从现在开始,在任何时机下读取到的 props,都是最初捕获到的那个 props。当父组件传入新的 props 来尝试重新渲染 ProfilePage 时,本质上是基于新的 props 入参发起了一次全新的函数调用,并不会影响上一次调用对上一个 props 的捕获。这样一来,便确保了渲染结果确实能够符合预期。
到此,相信大家不仅能够充分理解 Dan 所想要表达的“函数组件会捕获 render 内部的状态”这个结论,而且能够更进一步地意识到这样一件事情:函数组件真正地把数据和渲染绑定到了一起。
Hooks 的本质:一套能够使函数组件更强大、更灵活的“钩子”
React-Hooks 是什么?它是一套能够使函数组件更强大、更灵活的“钩子”。
前面已经说过,函数组件比起类组件“少”了很多东西,比如生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性,导致我们并不能使用函数这种形式,写出一个真正的全功能的组件。
React-Hooks 的出现,就是为了帮助函数组件补齐这些(相对于类组件来说)缺失的能力。
如果说函数组件是一台轻巧的快艇,那么 React-Hooks 就是一个内容丰富的零部件箱。“重装战舰”所预置的那些设备,这个箱子里基本全都有,同时它还不强制你全都要,而是允许你自由地选择和使用你需要的那些能力,然后将这些能力以 Hook(钩子)的形式“钩”进你的组件里,从而定制出一个最适合你的“专属战舰”。
二、代码简化
三、useEffect 则在一定程度上弥补了生命周期的缺席。
useEffect(()=>{
// 这里是 A 的业务逻辑
// 返回一个函数记为 B
return ()=>{
}
})
这段代码就会使得 React 在每一次渲染都去触发 A 逻辑,并且在卸载阶段去触发 B 逻辑。
useEffect(()=>{
// 这是回调函数的业务逻辑
// 若 xxx 是一个函数,则 xxx 会在组件卸载时被触发
return xxx
}, [num1, num2, num3])
这里给出的一个示意数组是 [num1, num2, num3]。首先需要说明,数组中的变量一般都是来源于组件本身的数据(props 或者 state)。若数组不为空,那么 React 就会在新的一次渲染后去对比前后两次的渲染,查看数组内是否有变量发生了更新(只要有一个数组元素变了,就会被认为更新发生了),并在有更新的前提下去触发 useEffect 中定义的副作用逻辑。
Class 的两大“痛点”:this 和生命周期
this
class Example extends Component {
state = {
name: '莫名',
age: '99';
};
changeAge() {
// 这里会报错
this.setState({
age: '100'
});
}
render() {
return <button onClick={this.changeAge}>{this.state.name}的年龄是{this.state.age}</button>
}
}
先不用关心组件具体的逻辑,就看 changeAge 这个方法:它是 button 按钮的事件监听函数。当点击 button 按钮时,希望它能够修改状态,但事实是,点击发生后,程序会报错。原因很简单,changeAge 里并不能拿到组件实例的 this,至于为什么拿不到,后续文章会有详细讲解,目前暂时跳过这一点。单就这个现象来说,略有一些 React 开发经验的朋友应该都会非常熟悉。
为了解决 this 不符合预期的问题,各路前端也是各显神通,之前用 bind、现在推崇箭头函数。但不管什么招数,本质上都是在用实践层面的约束来解决设计层面的问题。而现在有了 Hooks就不一样了,我们便可以在函数组件里放飞自我了(毕竟函数组件是不用关心 this 的)!
生命周期
①学习成本;
②不合理的逻辑规划方式。
对于第一点学过生命周期都会懂。下面着重说说这“不合理的逻辑规划方式”是如何被 Hooks 解决掉的
componentDidMount() {
// 1. 这里发起异步调用
// 2. 这里从 props 里获取某个数据,根据这个数据更新 DOM
// 3. 这里设置一个订阅
// 4. 这里随便干点别的什么
// ...
}
componentWillUnMount() {
// 在这里卸载订阅
}
componentDidUpdate() {
// 1. 在这里根据 DidMount 获取到的异步数据更新 DOM
// 2. 这里从 props 里获取某个数据,根据这个数据更新 DOM(和 DidMount 的第2步一样)
}
像这样的生命周期函数,它的体积过于庞大,做的事情过于复杂,会给阅读和维护者带来很多麻烦。最重要的是,这些事情之间看上去毫无关联,逻辑就像是被“打散”进生命周期里了一样。比如,设置订阅和卸载订阅的逻辑,虽然它们在逻辑上是有强关联的,但是却只能被分散到不同的生命周期函数里去处理,这无论如何也不能算作是一个非常合理的设计。
而在 Hooks 的帮助下,完全可以把这些繁杂的操作按照逻辑上的关联拆分进不同的函数组件里:我们可以有专门管理订阅的函数组件、专门处理 DOM 的函数组件、专门获取数据的函数组件等。Hooks 能够帮助我们实现业务逻辑的聚合,避免复杂的组件和冗余的代码。
状态复用
作者:啊丫丫
链接:https://juejin.cn/post/6918732763818360839
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:啊丫丫
链接:https://juejin.cn/post/6918732763818360839
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:啊丫丫
链接:https://juejin.cn/post/6918732763818360839
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:啊丫丫
链接:https://juejin.cn/post/6918731129059016717
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
网友评论