react——hooks(基础hook)

React Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

1.useState()

调用 useState 方法的时候做了什么? 它定义一个 “state 变量”。我们的变量叫 count, 但是我们可以叫他任何名字,比如 banana。这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。

useState 需要哪些参数? useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以我们传了 0 作为变量的初始 state。(如果我们想要在 state 中存储两个不同的变量,只需调用 useState() 两次即可。)

useState 方法的返回值是什么? 返回值为:当前 state 以及更新 state 的函数。这就是我们写 const [count, setCount] = useState() 的原因。这与 class 里面 this.state.countthis.setState 类似,唯一区别就是你需要成对的获取它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, {useState} from 'react';

export default function App() {

const [name,setName] = useState("sola")
const [age,setAge] = useState(100)
return (
<div>
<button onClick={() => {
setName("jerry");
setAge(12)
}}>click</button>
app -{name}
age -{age}
</div>
)
}

2.useEffect()

Effect Hook 可以让你在函数组件中执行副作用操作,有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。

useEffect 做了什么? 通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。在这个 effect 中,我们设置了 document 的 title 属性,不过我们也可以执行数据获取或调用其他命令式的 API。

为什么在组件内部调用 useEffect?useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。

useEffect 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

不需要清除的effect

1
2
3
4
5
6
7
function Example() {
const [count, setCount] = useState(0);

useEffect(() => {
document.title = `You clicked ${count} times`;
});
}

需要清除的effect

之前,我们研究了如何使用不需要清除的副作用,还有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!

如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它:

为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

React 何时清除 effect? React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 在执行当前 effect 之前对上一个 effect 进行清除。我们稍后将讨论为什么这将助于避免 bug以及如何在遇到性能问题时跳过此行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React, {Component, useEffect} from 'react';

class App extends Component {

state = {
isShow:false
}

render() {
return (
<div>
<button onClick={() => {
this.setState({
isShow:!this.state.isShow
})
}}>click</button>
{this.state.isShow && <Child/>}
</div>
);
}
}

function Child() {
useEffect(() => {
window.onresize = () => {
console.log('resize')
}
let timer = setInterval(() => {
console.log(123);
},2000)


return () => {
window.onresize = null
clearInterval(timer)
}
},[])

return <div>
child
</div>
}


export default App;

使用多个 Effect 实现关注点分离

Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});

const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}

通过跳过 Effect 进行性能优化

如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可。对于有清除操作的 effect 同样适用。

1
2
3
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

3.useContext()

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。举个例子,在下面的代码中,我们通过一个 “theme” 属性手动调整一个按钮组件的样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}

class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}

Context.Provider

1
<MyContext.Provider value={/* 某个值 */}>

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextTypeuseContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。

Class.contextType

挂载在 class 上的 contextType 属性可以赋值为由 React.createContext() 创建的 Context 对象。此属性可以让你使用 this.context 来获取最近 Context 上的值。你可以在任何生命周期中访问到它,包括 render 函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;

Context.Consumer

一个 React 组件可以订阅 context 的变更,此组件可以让你在函数式组件中可以订阅 context。

这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext()defaultValue

1
2
3
<MyContext.Consumer>
{value => /* 基于 context 值进行渲*/}
</MyContext.Consumer>

Context.displayName

context 对象接受一个名为 displayName 的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。

1
2
3
4
5
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import React, {Component, useContext, useEffect, useState} from 'react';
import axios from "axios";
import './css/03-communication.css'


const GlobalContext = React.createContext()

export default function App() {
const [filmList, setFilmlist] = useState([
{
"filmId": 5386,
"name": "我的姐姐",
"poster": "https://pic.maizuo.com/usr/movie/75c67d58c49822d782561cdcca65189c.jpg",
"actors": [
{
"name": "殷若昕",
"role": "导演",
"avatarAddress": "https://pic.maizuo.com/usr/movie/5c9574f920a21bfea0a35b0557c6dd43.jpg"
},
{
"name": "张子枫",
"role": "安然",
"avatarAddress": "https://pic.maizuo.com/usr/movie/2bddc157bd0faf6029bbae14ba8d92fc.jpg"
},
{
"name": "肖央",
"role": "舅舅",
"avatarAddress": "https://pic.maizuo.com/usr/movie/3ce5eeb8f056391d655c2d0a6ec35675.jpg"
},
{
"name": "金遥源",
"role": "弟弟",
"avatarAddress": "https://pic.maizuo.com/usr/movie/ecda1a57d366c843f28dc68293d4ae63.jpg"
},
{
"name": "王圣迪",
"role": "童年安然",
"avatarAddress": "https://pic.maizuo.com/usr/movie/a7eb39e0b652da91c4e3a1c1c7a17a85.jpg"
}
],
"director": "殷若昕",
"category": "剧情|家庭",
"synopsis": "电影讲述失去父母的姐姐在面对追求个人独立生活还是抚养弟弟的问题上展开的一段亲情故事。",
"filmType": {
"name": "2D",
"value": 1
},
"nation": "中国大陆",
"language": "",
"videoId": "",
"premiereAt": 1617321600,
"timeType": 3,
"runtime": 127,
"grade": "7",
"item": {
"name": "2D",
"type": 1
},
"isPresale": true,
"isSale": false
},
{
"filmId": 5391,
"name": "哥斯拉大战金刚",
"poster": "https://pic.maizuo.com/usr/movie/b624c348ee645c004b1e349dbe162ec9.jpg",
"actors": [
{
"name": "亚当·温加德",
"role": "导演",
"avatarAddress": "https://pic.maizuo.com/usr/movie/885b9e96f320408abfc9d172588e753a.jpg"
},
{
"name": "亚历山大·斯卡斯加德",
"role": "Nathan Lind",
"avatarAddress": "https://pic.maizuo.com/usr/movie/8e6b49c18a8311d8665bf013ea1aa73a.jpg"
},
{
"name": "米莉·波比·布朗",
"role": "Madison Russell",
"avatarAddress": "https://pic.maizuo.com/usr/movie/c05f1e2481e04f737cd91f4d03c9ff85.jpg"
},
{
"name": "丽贝卡·豪尔",
"role": "Ilene Andrews",
"avatarAddress": "https://pic.maizuo.com/usr/movie/290e19d60a61ecc877bcd20d1969c599.jpg"
},
{
"name": "布莱恩·泰里·亨利",
"role": "Bernie Hayes",
"avatarAddress": "https://pic.maizuo.com/usr/movie/323a79599476310db5bde745910857e4.jpg"
}
],
"director": "亚当·温加德",
"category": "动作|冒险",
"synopsis": "影片中,这两位宛如神衹一般强大的对手于一场壮观的战争中相遇,彼时世界命运正悬于一线。为了找到真正的家园,金刚与他的保护者们踏上了一次艰难的旅程。与他们一道前行的还有一个年轻的孤儿女孩——吉雅,这个女孩与金刚之间存在着一种独特而强大的紧密联系。但意想不到的是,他们在前行的航道上与愤怒的哥斯拉狭路相逢,也由此在全球引起了一系列破坏。一股无形的力量造成了这两只巨兽之间的巨大冲突,深藏在地心深处的奥秘也由此揭开。",
"filmType": {
"name": "2D",
"value": 1
},
"nation": "美国",
"language": "",
"videoId": "",
"premiereAt": 1616716800,
"timeType": 3,
"runtime": 113,
"grade": "7.2",
"item": {
"name": "2D",
"type": 1
},
"isPresale": true,
"isSale": false
},)
const [info, setInfo] = useState("")

return (
<GlobalContext.Provider value={{
call: 'phone', info: info, changeInfo: (value) => {
setInfo(value)
}
}}>
<div>
{
filmList.map(item =>
<FilmItem key={item.filmId} {...item} />
)
}

<FilmDetail/>
</div>
</GlobalContext.Provider>

)
}


function FilmItem(props) {
const value = useContext(GlobalContext)
console.log(value);
let {name, poster, grade, synopsis} = props
return (
<div className="filmItem" onClick={() => {
value.changeInfo(synopsis)
}}>
<img src={poster} alt=""/>
<h4>{name}</h4>
<div>观众评分:{grade}</div>
</div>
)
}


function FilmDetail() {
const value = useContext(GlobalContext)
return (
<div className="filmdetail">
detail-{value.info}
</div>
)
}