React 16.8发布后带来了一项新的玩意,React发生了大改变!!!解决了很多类组件的固有缺陷,这篇文章让大家快速熟悉并掌握最常用的两个Hook:userState 和 useEffect
了解使用的过程中,还能掌握一些背后的原理,顺便实现一个豆瓣电影吧榜单应用。
准备工作
阅读之前,希望同学已经做了一些准备
- 掌握了react基础知识
- 会使用react来写代码
开始
Hook是什么?
Hook 是什么? Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。稍后我们将学习其他 Hook。
为什么需要hooks?
1.hooks出现之前, 类组件提供了完整的状态管理和声明周期控制,通常用来承载复杂的业务逻辑。
2.函数组件则是纯粹的从数据到数据映射,对状态无感知。
hooks解决了什么问题?
1.令人头痛的this绑定
2.声明周期的不合理
3.数据共享的困局(需要借助复杂的状态管理器redux)
我们先看看官方介绍 Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
userState官方示例
import React, { useState } from 'react';
function Example() {
// 声明一个新的叫做 “count” 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
官方讲解的比较分散,链接:https://react.docschina.org/docs/hooks-intro.html,我会从实现一个简单项目来介绍使用
等价的 class示范
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
tip: 是不是觉得hooks代码清晰简单了很多
来看一个经典的计数器
function Counter() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
}
这里问大家一个问题?
点击 Click me 按钮,count增加到 3, 点击 Show alert 按钮,在setTimeout 结束之前,继续点击 Click me 按钮,count增加到 5, 这个时候 alert 是多少?
答案:alter 是 3
你答对了么?
简单解释一下:
- 每次渲染相互独立,状态,时间函数都是独立的
- 我们再count为3的时候 触发handleAlertClick方法,这个函数记住的就是count = 3
- setTimeout 三秒后结束 alter 自然是3
userEffect浅析
官方文档介绍
useEffect(effectFn, deps)
使用 useEffect 我们需要忘记以前写class的时候 使用到的生命周期,函数组件跟类组件时不同的世界
- React会在每次渲染完后运行Effect,而依赖数组就是用来控制是否触发Effect,从而减少不必要的计算,从而优化性能
1.1.简单说明:只要依赖数组中的每一项与上一次渲染相比没有改变,此次Effect就不会被执行
1.2.类比:以前我们写class的时候 会使用 componentDidUpdate 来控制是否重新计算 - useEffect 跟class的生命周期相比,有哪些不同?
2.1.将初次渲染(componentDidMount)、重渲染(componentDidUpdate)和销毁(componentDidUnmount)三个阶段的逻辑用一个统一的 API 去解决
2.2.简单举例:只需要渲染一次的 我们可以直接指定 deps 为空数组 [ ]
实战小应用
1.通过Creat React App 初始化项目:npx create-react-app my-app-with-hooks
2.样式使用到了 Ant Design: yarn add antd
3.App.js源码:
import React, { useState, useEffect } from "react";
import "./App.css";
import 'antd/dist/antd.css';
import { Button, Spin, Input, Select, Table, Rate } from 'antd';
const { Option } = Select;
const columns = [
{
title: '电影名',
dataIndex: 'title',
render: (text, record) => (
<Button type="link" onClick={() => go(record.url)}>{record.title}</Button>
),
},
{
title: '海报',
dataIndex: 'url',
render: (text, record) => (
<img src={record.cover} style={{height: 190}} alt=""/>
),
},
{
title: '星级',
dataIndex: 'rate',
render: (text, record) => {
return <Rate disabled defaultValue={Number(record.rate)/2}/>
}
},
];
const BASE_URL = "https://movie.douban.com/j";
function getFetchUrl(key) {
return `${BASE_URL}/search_subjects?type=${key}&tag=热门&page_limit=50&page_start=0`
}
function go(url) {
window.open(url)
}
function App() {
const [countries, setCountries] = useState([]);
const [key, setKey] = useState("movie");
const [inputText, setInput] = useState("");
useEffect(() => {
const fetchCountries = async () => {
const response = await fetch(getFetchUrl(key));
const data = await response.json();
setCountries(data.subjects);
};
fetchCountries();
}, [key]); // deps 加入了 key监听, 所以每次key改变 都会调用这个 useEffect
function handleChange(value) {
setKey(value)
}
return (
<div className='App'>
<h1>本周热门排行</h1>
<Input style={{ width: 180}} placeholder="请输入搜索的类型" value={inputText} onChange={e => setInput(e.target.value)} />
<Button style={{marginLeft: 10, marginRight: 10}} type="primary" onClick={() => setKey(inputText)}>搜索</Button>
<Select defaultValue="电影" style={{ width: 120 }} onChange={handleChange}>
<Option value="tv">电视</Option>
<Option value="moive">电影</Option>
<Option value="radio" disabled>
广播
</Option>
</Select>
<Table dataSource={countries} columns={columns} rowKey={(record, index) => `complete${record.id}${index}`} style={{ marginTop: 20}}/>;
{
countries.length === 0 ? <Spin /> : <div/>
}
</div>
);
}
export default App;
效果图:
相信大家看见代码应该都明白了,我这边还是简单说明一下
1.这里我创建了两个状态 key(用来筛选 tv 跟 movie)跟 countries (列表数据填充)
2.通过 useEffect 钩子进行数据获取,但是第二个参数 deps 这里依赖了 key, 只要当 key 改变,useEffect钩子就会重新触发
3.最后将数据 setCountries 赋值给 countries
注意事项
1.不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
2.只能在React函数式组件或自定义Hook中使用Hook。
3.借助eslint-plugin-react-hooks的 ESLint 插件来强制执行这两条规则,违反hooks规则,会有eslint提示
4.npm install eslint-plugin-react-hooks --save-dev
5.eslint配置
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
"react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
}
}
useMemo简单实例理解:
父组件
import React, { useState, useEffect } from "react";
import "./App.css";
import Child from './components/Child'
const BASE_URL = "https://movie.douban.com/j";
function App() {
const [price, setPrice] = useState('价格');
const [content,setContent] = useState('内容');
return (
<div className='App'>
<button onClick={() => setPrice(price + 1)}>price</button>
<button onClick={() => setContent(content + 2)}>content</button>
<Child price={price} content={content}></Child>
</div>
);
}
export default App;
子组件
mport React, { useState, useEffect, useMemo } from "react";
function Child({ price, content }) {
function changePrice(price) {
console.log('price: ', price);
return price + '元'
}
// const otherPrice = useMemo(() => changePrice(price), [price])
const otherPrice = changePrice(price)
return (
<>
<div>{otherPrice}</div>
<div>{content}</div>
</>
)
}
export default Child;
从上述代码可以看到,子组件的price 跟 content ,触发setPrice 或者 setContent 都会触发 changePrice方法,这意味着性能损耗,做了无用功,
当我们触发 setContent 的时候 希望不会触发 changePrice方法, 这个时候 使用 useMemo 就可以完美解决了。