过去,React 中的函数组件都被称为无状态函数式组件(stateless functional component),这是因为函数组件没有办法拥有自己的状态,只能根据 Props 来渲染 UI ,其性质就相当于是类组件中的 render 函数,虽然结构简单明了,但是作用有限。
但自从 React Hooks 横空出世,函数组件也拥有了保存状态的能力,而且也逐渐能够覆盖到类组件的应用场景,因此可以说 React Hooks 就是未来 React 发展的方向。
我们知道组件化的思想就是将一个复杂的页面/大组件,按照不同层次,逐渐抽象并拆分成功能更纯粹的小组件,这样一方面可以减少代码耦合,另外一方面也可以更好地复用代码;但实际上,在使用 React 的类组件时,往往难以进一步分拆复杂的组件,这是因为逻辑是有状态的,如果强行分拆,会令代码复杂性急剧上升;如使用 HOC 和 Render Props 等设计模式,这会形成“嵌套地狱”,使我们的代码变得晦涩难懂。
这其实也是上一点的延续:要给一个拥有众多状态逻辑的组件写单元测试,无疑是一件令人崩溃的事情,因为需要编写大量的测试用例来覆盖代码执行路径。
对于类组件,我们需要在组件提供的生命周期钩子中处理状态的初始化、数据获取、数据更新等操作,处理起来本身逻辑就比较复杂,而且各种“副作用”混在一起也使人头晕目眩,另外还很可能忘记在组件状态变更/组件销毁时消除副作用。
我认为 React Hooks 的亮点不在于 React 官方提供的那些 api ,那些 API 只是一些基础的能力;其亮点还是在于自定义 Hooks —— 一种封装复用的设计模式。
例如,一个页面上往往有很多状态,这些状态分别有各自的处理逻辑,如果用类组件的话,这些状态和逻辑都会混在一起,不够直观:
class Com extends React.Component {
state = {
a: 1,
b: 2,
c: 3,
}
componentDidMount() {
handleA()
handleB()
handleC()
}
}
而使用 React Hooks 后,我们可以把状态和逻辑关联起来,分拆成多个自定义 Hooks ,代码结构就会更清晰:
function useA() {
const [a, setA] = useState(1)
useEffect(() => {
handleA()
}, [])
return a
}
function useB() {
const [b, setB] = useState(2)
useEffect(() => {
handleB()
}, [])
return b
}
function useC() {
const [c, setC] = useState(3)
useEffect(() => {
handleC()
}, [])
return c
}
function Com() {
const a = useA()
const b = useB()
const c = useC()
}
我们除了可以利用自定义 Hooks 来拆分业务逻辑外,还可以拆分成复用价值更高的通用逻辑,比如说目前比较流行的 Hooks 库:react-use;另外,React 生态中原来的很多库,也开始提供 Hooks API ,如 react-router 。
React 提供了大量的组件生命周期钩子,虽然在日常业务开发中,用到的不多,但光是 componentDidUpdate 和 componentWillUnmount 就让人很头痛了,一不留神就忘记处理 props 更新和组件销毁需要处理副作用的场景,这不仅会留下肉眼可见的 bug ,还会留下一些内存泄露的隐患。
类 MVVM 框架讲究的是数据驱动,而生命周期这种设计模式,就明显更偏向于传统的事件驱动模型;当我们引入 React Hooks 后,数据驱动的特性能够变得更纯粹。
下面我们以一个非常典型的列表页面来举个例子:
class List extends Component {
state = {
data: []
}
fetchData = (id, authorId) => {
// 请求接口
}
componentDidMount() {
this.fetchData(this.props.id, this.props.authorId)
// ...其它不相关的初始化逻辑
}
componentDidUpdate(prevProps) {
if (
this.props.id !== prevProps.id ||
this.props.authorId !== prevProps.authorId // 别漏了!
) {
this.fetchData(this.props.id, this.props.authorId)
}
// ...其它不相关的更新逻辑
}
render() {
// ...
}
}
上面这段代码有3个问题:
如果改成用 React Hooks 来实现,问题就能得到很大程度上的解决了:
function List({ id, authorId }) {
const [data, SetData] = useState([])
const fetchData = (id, authorId) => {}
useEffect(() => {
fetchData(id, authorId)
}, [id, authorId])
}
改用 React Hooks 后:
最常见的副作用莫过于绑定 dom 事件:
class List extends React.Component {
handleFunc = () => {}
componentDidMount() {
window.addEventListener('scroll', this.handleFunc)
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleFunc)
}
}
这块也还是会有上述说的,影响高内聚的问题,改成 React Hooks :
function List() {
useEffect(() => {
window.addEventListener('scroll', this.handleFunc)
}, () => {
window.removeEventListener('scroll', this.handleFunc)
})
}
而且比较绝的是,除了在组件销毁的时候会触发外,在依赖项变化的时候,也会执行清除上一轮的副作用。
在使用类组件的时候,我们需要利用 componentShouldUpdate 这个生命周期钩子来判断当前是否需要重新渲染,而改用 React Hooks 后,我们可以利用 useMemo 来判断是否需要重新渲染,达到局部性能优化的效果:
function List(props) => {
useEffect(() => {
fetchData(props.id)
}, [props.id])
return useMemo(() => (
// ...
), [props.id])
}
在上面这段代码中,我们看到最终渲染的内容是依赖于props.id,那么只要props.id不变,即便其它 props 再怎么办,该组件也不会重新渲染。
在我们刚开始使用 React Hooks 的时候,经常会遇到这样的场景:在某个事件回调中,需要根据当前状态值来决定下一步执行什么操作;但我们发现事件回调中拿到的总是旧的状态值,而不是最新状态值,这是怎么回事呢?
function Counter() {
const [count, setCount] = useState(0);
const log = () => {
setCount(count + 1);
setTimeout(() => {
console.log(count);
}, 3000);
};
return (
<button onClick={log}>报数</button>
);
}
/*
如果我们在三秒内连续点击三次,那么count的值最终会变成 3,而随之而来的输出结果是?
0
1
2
*/
“这是 feature 不是 bug ”,哈哈哈,说是 feature 可能也不太准确,因为这不正是 javascript 闭包的特性吗?当我们每次往setTimeout里传入回调函数时,这个回调函数都会引用下当前函数作用域(此时 count 的值还未被更新),所以在执行的时候打印出来的就会是旧的状态值。
那为啥类组件中,每次都能取到最新的状态值呢?这是因为我们在类组件中取状态值都是从this.state里取的,这相当于是类组件的一个执行上下文,永远都是保持最新的。
通过useRef创建的对象,其值只有一份,而且在所有 Rerender 之间共享。
听上去,这 useRef 其实跟 this.state 很相似嘛,都是一个可以一直维持的值,那我们就可以用它来维护我们的状态了:
function Counter() {
const count = useRef(0);
const log = () => {
count.current++;
setTimeout(() => {
console.log(count.current);
}, 3000);
};
return (
<button onClick={log}>报数</button>
);
}
/*
3
3
3
*/
近日,据 MIT Technology Review 报道,一位名为“Repairnator”的机器人在 GitHub 上“卧底”数月,查找错误并编写和提交修复补丁,结果有多个补丁成功通过并被采纳,这位 Repairnator 到底是如何拯救程序员于水火的呢?
你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。
我们将userReducer函数返回的原始dispath命名为origin_dispatch,自定义dispatch函数,当action为函数的时候,我们执行action函数,并将origin_dispatch当作参数传进去;action不是函数,直接调用origin_dispatch,不做处理
使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。
从 React Hooks 正式发布到现在,我一直在项目使用它。但是,在使用 Hooks 的过程中,我也进入了一些误区,导致写出来的代码隐藏 bug 并且难以维护。这篇文章中,我会具体分析这些问题,并总结一些好的实践,以供大家参考
Hooks 的 API 可以参照 React 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。 注意:Rax 的写法和 React 是一致的
以下是上一代标准写法类组件的缺点,也正是hook要解决的问题,型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
Hooks出来已经有段时间了,相信大家都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个 setInterval 的坑,和小伙伴们分享下解决方案。写个 count 每秒自增的定时器,如下写法结果,界面上 count 为 1 ?
对于 React 16.7 中新的 hooks 系统在社区中引起的骚动,我们都有所耳闻了。人们纷纷动手尝试,并为之兴奋不已。一想到 hooks 时它们似乎是某种魔法,React 以某种甚至不用暴露其实例
9月份开始,使用了React16.8的新特性React Hooks对项目进行了重构,果然,感觉没有被辜负,就像阮一峰老师所说的一样,这个 API 是 React 的未来。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!