React Hook
Hook 是什么? React Hook 又是什么?
Hook 中文的意思是钩子。其实,在计算机编辑领域有专业解释。看看来自维基百科的解释:
钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。
处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。
React Hook 咱是不是就可以这样理解: react 组件加载、渲染、卸载过程中的拦截处理程序。
React Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)
React Hook 是为了解决什么问题?
咱们得想,程序设计的升级是为了什么?计算机性、人性。
计算机性,就是让计算机更好的执行程序,不要崩溃。
人性,就是怎么让程序员更好的写代码:条理性、易读性、维护性、扩展性!
根据 React 官网的描述:Hook 主要是为了解决组件内状态逻辑的简化和组件间状态逻辑的复用。
- 组件内状态逻辑的简化。比如:状态管理的简化,生命周期的简化,这些下面会通过三大基础 hook 来体现。
- 组件间状态逻辑的复用,用自定义 hook 就可以体现出来。
常用的 React Hook 有哪些?
我们将用一个 TodoList 项目来简单演示一下,代码地址在文章底部。
useState
赋予函数组件状态管理功能
V16.8.0 版本之前,函数组件被称为无状态组件。只能通过 props 中的数据进行渲染。
而现在,useState 赋予函数组件内部状态管理功能。
// 初始化一些组件状态
const [inputItem, setInputItem] = useState("");
const [listFlag, setListFlag] = useState("all");
const [allList, setAllList] = useState([]);
// ...
// 通过交互来改变状态
const handleInputItem = e => {
setInputItem(e.target.value);
};
是不是很简单!
useEffect
赋予函数组件类似生命周期的功能
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
"在组件第一次渲染和之后更新时,都会执行里面的代码程序。可以设置第二个参数来控制是不是执行,依赖哪些数据变化而执行。"
另外,Effect 分为:需要清除和不需要清除。
改变状态,发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
订阅外部数据源,时间触发器等,这些需要清除,否则会引起内存泄露!
- 不需要做清除副作用
// 初始待办事项列表数据
useEffect(() => {
const listData = [
{
title: "敲代码",
isCompleted: false
},
{
title: "写PPT",
isCompleted: true
},
{
title: "分享",
isCompleted: false
}
];
setAllList(listData);
}, []); // 一定要注意这里,如果不加第二个参数 [],会导致组件一直不停执行这个副作用里的程序。
// 设置列表状态
useEffect(() => {
setListFlag("activing");
}, []);
- 需要清除副作用
let timer = null;
let count = 0;
// 需要用来证明卸载时执行的程序
useEffect(() => {
generateTimer();
return clearTimer; //如果不加这句,可能会导致,count 一直会变化,即使组件已经卸载
}, []);
const generateTimer = () => {
timer = setTimeout(() => {
localStorage.setItem("test-timer", count++);
generateTimer();
}, 1000);
};
const clearTimer = () => {
clearTimeout(timer);
};
useContext
赋予函数组件获取最近上下文的功能
如果你在接触 Hook 前已经对 context API 比较熟悉,那应该可以理解,useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>。
useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。
// context.js
import React, { createContext, useState } from "react";
const GlobalContext = createContext();
export default GlobalContext;
// App.js 外层组件
function App() {
const [username, setUsername] = useState("gpd");
return (
<div className="App">
<GlobalContext.Provider
value={{ username: username, updateUsername: setUsername }}
>
<Router>
<header>
<ul className="nav-menu">
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/todoList">待办事项-原生实现</Link>
</li>
<li>
<Link to="/todoListHook/gpd">待办事项- Hook 实现</Link>
</li>
</ul>
</header>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/todoList" exact component={TodoList} />
<Route path="/todoListHook/:user" exact component={TodoListHook} />
</Switch>
</Router>
</GlobalContext.Provider>
</div>
);
}
import React, { Component, useState, useEffect, useContext } from "react";
import GlobalContext from "../context.js";
import usePageinfo from "../hooks/usePageinfo";
import useTimer from "../hooks/useTimer";
import "./TodoList.css";
const Home = () => {
const globalContext = useContext(GlobalContext); // 这里进行引用全局 context
console.log(globalContext);
usePageinfo();
const viewTime = useTimer();
return (
<div>
<h1>这是 Home 页面!</h1>
<h2>页面已经被浏览:{viewTime} 秒!</h2>
<h2>当前用户是:{globalContext.username}</h2> <!-- 全局 context 里的状态 -->
<button
className="update"
type="button"
onClick={() => globalContext.updateUsername("sjs")} // 使用全局 context 里更新方法来改变全局状态
>
修改用户名为 “sjs”
</button>
</div>
);
};
export default Home;
如何自定义一个 React Hook ?
将多个组件中可复用的逻辑提取到一个函数中。
Demo 项目中写了一个页面被浏览了多长时间的例子。因为,所有的页面组件都共用这个逻辑,所以把它提取了出来。
import React, { Component, useState, useEffect } from "react";
// 返回一个计时时间
const useTimer = () => {
const [count, setCount] = useState(0);
let timer = null;
// 需要用来证明卸载时执行的程序
useEffect(() => {
generateTimer();
return clearTimer;
}); // 这里就不要第二个参数 [],否则只执行一次后就停止了
const generateTimer = () => {
timer = setTimeout(() => {
setCount(count + 1);
generateTimer();
}, 1000);
};
const clearTimer = () => {
clearTimeout(timer);
};
return count;
};
export default useTimer;
// pages/Home.js
const Home = () => {
const globalContext = useContext(GlobalContext);
console.log(globalContext);
usePageinfo();
const viewTime = useTimer(); //看这里,用到自定义组件
return (
<div>
<h1>这是 Home 页面!</h1>
<h2>页面已经被浏览:{viewTime} 秒!</h2> <!-- 这里用来展示 -->
<h2>当前用户是:{globalContext.username}</h2>
<button
className="update"
type="button"
onClick={() => globalContext.updateUsername("sjs")}
>
修改用户名为 “sjs”
</button>
</div>
);
};
// pages/TodoListHook.js
const TodoListHook = () => {
usePageinfo();
const viewTime = useTimer(); //看这里,用到自定义组件
//...
return (
<div className="todoList">
<h2>页面已经被浏览:{viewTime} 秒!</h2> <!-- 这里用来展示 -->
<!-- ... -->
</div>
);
};
当然,如果展示方式如果完全一样,可以做成一个组件即可。这里的区分就是: hook 一般用来做数据处理。
看一下 React router 提供的 React Hook 。
import React, { Component, useState, useEffect } from "react";
import {
useParams,
useHistory,
useLocation,
useRouteMatch
} from "react-router-dom";
const usePageinfo = () => {
const routeParams = useParams();
const routeHistory = useHistory();
const routeLocation = useLocation();
const routeMatch = useRouteMatch();
console.log("useParams:");
console.log(routeParams);
console.log("useHistory:");
console.log(routeHistory);
console.log("useLocation:");
console.log(routeLocation);
console.log("useMatch:");
console.log(routeMatch);
};
export default usePageinfo;
可以看到非常简单地就能拿到 params、history、location、match。不用再用 props 一层层传递下来。
回顾一下,本篇文章要讲的。
- 咱们介绍了 hook 编程,怎么更好地理解 react hook 。
- 简单介绍官方三大基础 hook:useState、useEffect、useContext。
- 实现一个自定义 hook,页面浏览时长计时。
- 查看了第三方库 react router 怎么运用的 react hook。
欢迎大家拍砖,积极讨论。
项目代码地址:github.com/peidongGuo/…