React Hooks 从入门到上手

46次阅读

共计 4908 个字符,预计需要花费 13 分钟才能阅读完成。

前言
楼主最近在整理 React Hooks 的一些资料,为项目重构作准备,下午整理成了这篇文章。如果之前没接触过相关概念,那么通过这篇文章,你将会了什么是 React Hooks , 它是做什么的,以及如何使用。
下面我会用一个具体的例子来说明,通过这个例子,你将了解:

如何使用 React Hooks

如何用 React Class components 实现同样的逻辑

快速开始
先快速搭建一个项目:
npx create-react-app exploring-hooks
Demo in setState
import React, {Component} from “react”;

export default class Button extends Component {
state = {buttonText: “Click me, please”};

handleClick = () => {
this.setState(() => {
return {buttonText: “Thanks, been clicked!”};
});
};

render() {
const {buttonText} = this.state;
return <button onClick={this.handleClick}>{buttonText}</button>;
}
}

功能非常简单:点一下按钮,就更新 button 的 text。
Demo in Hooks
这里,我们将不再使用 setState 和 ES6 Class. 轮到我们的 Hooks 登场了:
import React, {useState} from “react”;

引入 useState 就意味着我们将要把一些状态管理置于组件内部,而且我们的 React Component 将不再是一个 ES6 class,取而代之的是一个简单的纯函数。
引入 useState 之后,我们将从中取出一个含有两个元素的数组:
const [buttonText, setButtonText] = useState(“Click me, please”);

如果对这个语法有疑问,可以参考 ES6 解构.
这两个值的名字,你可以随意取,和 React 无关,但是还是建议你根据使用的目的取一个足够具体和清晰的名字。
就比如上面写的,一个代表是 buttonText 的 值, 另一个代表是 setButtonText 的 更新函数。
给 useState 传入的是一个初始值,比如,这个按钮的最初要显示的是:Click me, please。
这个简单的例子的代码全貌:

import React, {useState} from “react”;

export default function Button() {
const [buttonText, setButtonText] = useState(“Click me, please”);

function handleButtonClick() {
return setButtonText(“Thanks, been clicked!”);
}

return <button onClick={handleButtonClick}>{buttonText}</button>;
}

下面我们将介绍如何使用 Hooks 获取数据。
使用 React Hooks 获取数据
在这之前,我们都是在 componentDidMount 函数里调 API:
import React, {Component} from “react”;

export default class DataLoader extends Component {

state = {data: [] };

async componentDidMount() {
try {
const response = await fetch(`https://api.coinmarketcap.com/v1/ticker/?limit=10`);
if (!response.ok) {
throw Error(response.statusText);
}
const json = await response.json();
this.setState({data: json});
} catch (error) {
console.log(error);
}
}

render() {
return (
<div>
<ul>
{this.state.data.map(el => (
<li key={el.id}>{el.name}</li>
))}
</ul>
</div>
);
}
}
这种代码大家想必都非常熟悉了,下面我们用 Hooks 来重写:

import React, {useState, useEffect} from “react”;

export default function DataLoader() {
const [data, setData] = useState([]);

useEffect(() => {
fetch(“http://localhost:3001/links/”)
.then(response => response.json())
.then(data => setData(data));
});

return (
<div>
<ul>
{data.map(el => (
<li key={el.id}>{el.title}</li>
))}
</ul>
</div>
);
}
运行一下就会发现,哎呦,报错了,无限循环:

原因其实也非常简单,useEffect 存在的目的 和 componentDidMount, componentDidUpdate, and componentWillUnmount 是一致的,每次 state 变化 或者 有新的 props 进来的时候,componentDidUpdate componentDidUpdate` 都会执行。
要解决这个 “bug” 也非常简单, 给 useEffect 传入一个空数组作为第二个参数:
useEffect(() => {
fetch(“http://localhost:3001/links/”)
.then(response => response.json())
.then(data => setData(data));
},[]); // << super important array

关于 Hook 的详细信息可以参考:Using the Effect Hook
看到这你可能会按捺不住内心的小火苗,要去重构项目,个人还不建议这么做,因为接下来的几个版本中可能会有变化,就像 Ryan Florence 建议的:
Hooks are not the endgame for React data loading.Data loading is probably the most common effect in an app.
Don’t be in a big hurry to migrate to hooks for data unless you’re okay migrating again when suspense for data is stable.
Own your churn.
— Ryan Florence (@ryanflorence) February 12, 2019

无论怎么说,useEffect 的出现还是一件好事。
能把 Hooks 用于 Render props 吗
能显然是能的,不过没什么意义,比如把上面的代码改一下:
import React, {useState, useEffect} from “react”;

export default function DataLoader(props) {
const [data, setData] = useState([]);

useEffect(() => {
fetch(“http://localhost:3001/links/”)
.then(response => response.json())
.then(data => setData(data));
}, []);

return props.render(data)
}

从外部传入一个 render 即可,但是这样做毫无意义:Reack Hooks 本身就是为了解决组件间逻辑公用的问题的。
定义你的 React Hook
还是上面的例子,我们把取数据的逻辑抽出来:
// useFetch.tsx
import {useState, useEffect} from “react”;

export default function useFetch(url) {
const [data, setData] = useState([]);

useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => setData(data));
}, []);

return data;
}

在其他组件中引用:
import React from “react”;
import useFetch from “./useFetch”;

export default function DataLoader(props) {
const data = useFetch(“http://localhost:3001/links/”);
return (
<div>
<ul>
{data.map(el => (
<li key={el.id}>{el.title}</li>
))}
</ul>
</div>
);
}

React Hooks 的本质
上面我们说到 Reack Hooks 本身就是为了解决组件间逻辑公用的问题的。
回顾我们现在的做法,几乎都是面向生命周期编程:

Hooks 的出现是把这种面向生命周期编程变成了面向业务逻辑编程,让我们不用再去关注生命周期:

图片来源
而且,最新的 React 中,预置了大量的 Hooks,最重要两个的就是:useState and useEffect.
useState 使我们在不借助 ES6 class 的前提下,在组件内部使用 state 成为可能。
useEffect 取代了 componentDidMount, componentDidUpdate, and componentWillUnmount, 提供了一个统一的 API。
除了这两个之外,可以在官方文档中了解更多:

一个显而易见的事实是,过不来了多久,我们就会有三种创建 React components 的姿势:

functional components
class components
functional components with hooks

作为一个 React 忠实粉丝,看到这些积极的变化实在是令人感到愉悦。
Hooks 更多学习资源
还有很多帮助我们更好的学和掌握 React Hooks, 也在这里分享一下:
首先还是官方文档:Introducing Hooks,Hooks at a Glance 是稍微深入一些的内容。
然后是一个入门教程:Build a CRUD App in React with Hooks.
关于状态管理,还有一个比较有趣的文章:useReducer, don’t useState
比较有意思的是,我们最后会大量使用 useReducer,形势和 Redux 非常类似:
function reducer(state, action) {
const {past, future, present} = state
switch (action.type) {
case ‘UNDO’:
const previous = past[past.length – 1]
const newPast = past.slice(0, past.length – 1)
return {
past: newPast,
present: previous,
future: [present, …future],
}
case ‘REDO’:
const next = future[0]
const newFuture = future.slice(1)
return {
past: […past, present],
present: next,
future: newFuture,
}
default:
return state
}
}

这也从侧面证明了 Redux 在社区中的影响力(其实这两个东西的核心开发者是同一个人)。
总结

Hooks 的出现简化了逻辑,把面向生命周期编程变成了面向业务逻辑编程,为逻辑复用提供了更多可能。
Hooks 是未来的方向。

大概就是这些,希望能对大家有些启发和帮助。
才疏学浅,行文若有纰漏,还请各位大大帮忙指正,谢谢。

正文完
 0