Skip to content

Commit 1ba528f

Browse files
MuYunyunmujue
authored and
mujue
committed
docs: 新人培训
1 parent 851866a commit 1ba528f

File tree

8 files changed

+10463
-1
lines changed

8 files changed

+10463
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

README.md

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,146 @@
1-
# ReactHooksTraining
1+
Hooks 新人培训演讲大纲
2+
3+
### React Logo 与 Hooks
4+
5+
![](http://with.muyunyun.cn/ddbdcec2fc39ba350fc74647f4fad6f5.jpg-300)
6+
7+
React 的 logo 是一个原子图案, 原子组成了物质。类似的, React 就如原子般构成了页面的表现; 而 Hooks 就如夸克, 其更接近 React 本质的样子, 但是直到 4 年后的今天才被真正设计出来。 —— Dan in React Conf(2018)
8+
9+
### why Hooks?
10+
11+
一: `多个组件间逻辑复用`: 在 Class 中使用 React 不能将带有 state 的逻辑给单独抽离成 function, 其只能通过嵌套组件的方式来解决多个组件间逻辑复用的问题, 基于嵌套组件的思想存在 [HOC](https://github.com/MuYunyun/blog/blob/master/React/从0到1实现React/8.HOC探索.md)`render props` 两种设计模式。但是这两种设计模式是否存在缺陷呢?
12+
13+
* 嵌套地狱, 当嵌套层级过多后, 数据源的追溯会变得十分困难, 导致定位 bug 不容易; (hoc、render props)
14+
15+
![](http://with.muyunyun.cn/b9147e8bd39e7badccc3190fb473755f.jpg)
16+
17+
* 性能, 需要额外的组件实例存在额外的开销; (hoc、render props)
18+
* 命名重复性, 在一个组件中同时使用多个 hoc, 不排除这些 hoc 里的方法存在命名冲突的问题; (hoc)
19+
20+
二: `单个组件中的逻辑复用`: Class 中的生命周期 `componentDidMount``componentDidUpdate` 甚至 `componentWillUnMount` 中的大多数逻辑基本是类似的, 必须拆散在不同生命周期中维护相同的逻辑对使用者是不友好的, 这样也造成了组件的代码量增加。
21+
22+
![](http://with.muyunyun.cn/0c94989b2eced65c368ff2389464fd0a.jpg-400)
23+
24+
![](http://with.muyunyun.cn/d21d7974dbec9a49603e2211b354496c.jpg-400)
25+
26+
三: Class 的其它一些问题: 在 React 使用 Class 需要书写大量样板, 用户通常会对 Class 中 Constructor 的 bind 以及 this 的使用感到困惑; 当结合 class 与 TypeScript 一起使用时, 需要对 defaultValue 做额外声明处理; 此外 React Team 表示 Class 在机器编译优化方面也不是很理想。
27+
28+
### React Hooks 使用中的问题
29+
30+
#### 闭包陷阱
31+
32+
* demo1: 闭包陷阱1。 [Demo 地址](https://codesandbox.io/s/22y21468r)
33+
34+
1. Hooks/class demo 对比演示;
35+
2. Hooks/class 互相切换为对方的形态;
36+
37+
结论: 问题不在于是使用 Hooks 还是 class, 本质是受到闭包的影响。
38+
39+
* demo2: 闭包陷阱2
40+
41+
由 Class 转换过来的用户习惯 `setCount(count + 1))` 的方式。但在 Hooks 中这样子使用会产生闭包问题导致 `count` 不会增加。
42+
43+
```js
44+
function Demo() {
45+
const [count, setCount] = React.useState(0)
46+
useEffect(() => {
47+
const id = setInterval(() => {
48+
setCount(count + 1)
49+
}, 2000)
50+
51+
return () => {
52+
clearInterval(id)
53+
}
54+
}, [])
55+
return (
56+
<div>Count: {count}</div>
57+
)
58+
}
59+
```
60+
61+
提供 3 种解法。用户说还是想用 `setCount(count + 1)` 的形式怎么办
62+
63+
引出为此提供 `useInterval` 钩子, 顺利过渡到 `beast-hooks`
64+
65+
```js
66+
function useInterval(callback, delay: number) {
67+
const cbRef = useRef({})
68+
useEffect(() => {
69+
cbRef.current = callback
70+
}, [callback])
71+
useEffect(() => {
72+
setInterval(() => {
73+
cbRef.current()
74+
}, delay)
75+
}, [delay])
76+
}
77+
```
78+
79+
用法:
80+
81+
```js
82+
function Demo() {
83+
const [count, setCount] = React.useState(0)
84+
85+
useInterval(() => {
86+
setCount(count + 1)
87+
}, 2000)
88+
89+
return (<div>Count: {count}</div>)
90+
}
91+
```
92+
93+
### React Hooks 内部探究
94+
95+
`useState``useReducer` 为例
96+
97+
#### 使用 useState 实现 useReducer
98+
99+
```js
100+
import * as React from 'react'
101+
const { useState, useRef, useCallback } = React
102+
103+
function useReducer(reducer, initialState) {
104+
const [state, setState] = useState(initialState)
105+
const reducerRef = useRef(reducer)
106+
const stateRef = useRef(state)
107+
108+
const dispatch = useCallback((action) => {
109+
setState(reducerRef.current(stateRef.current, action))
110+
}, [])
111+
112+
useEffect(() => {
113+
reducerRef.current = reducer
114+
}, [reducer])
115+
116+
useEffect(() => {
117+
stateRef.current = state
118+
}, [state])
119+
120+
return [state, dispatch]
121+
}
122+
```
123+
124+
#### 使用 useReducer 实现 useState
125+
126+
```js
127+
import * as React from 'react'
128+
const { useReducer, useCallback } = React
129+
130+
function useState(initialState) {
131+
const [state, dispatch] = useReducer((state, action) => {
132+
return action
133+
}, initialState)
134+
135+
const setState = useCallback(
136+
(newState) => dispatch(newState), []
137+
)
138+
139+
return [state, setState]
140+
}
141+
```
142+
143+
### 相关链接
144+
145+
* [React Hooks 深入系列](https://github.com/MuYunyun/blog/blob/master/React/React_Hooks深入系列.md)
146+
* [React Hooks 设计模式](https://github.com/MuYunyun/blog/blob/master/React/React_Hooks设计模式.md)

package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "new",
3+
"version": "1.0.0",
4+
"description": "",
5+
"keywords": [],
6+
"main": "src/index.js",
7+
"dependencies": {
8+
"react": "16.8.6",
9+
"react-dom": "16.8.6",
10+
"react-scripts": "2.1.8"
11+
},
12+
"devDependencies": {
13+
"typescript": "3.3.3"
14+
},
15+
"scripts": {
16+
"start": "react-scripts start",
17+
"build": "react-scripts build",
18+
"test": "react-scripts test --env=jsdom",
19+
"eject": "react-scripts eject"
20+
},
21+
"browserslist": [
22+
">0.2%",
23+
"not dead",
24+
"not ie <= 11",
25+
"not op_mini all"
26+
]
27+
}

public/index.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7+
<meta name="theme-color" content="#000000">
8+
<!--
9+
manifest.json provides metadata used when your web app is added to the
10+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
11+
-->
12+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
13+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
14+
<!--
15+
Notice the use of %PUBLIC_URL% in the tags above.
16+
It will be replaced with the URL of the `public` folder during the build.
17+
Only files inside the `public` folder can be referenced from the HTML.
18+
19+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
20+
work correctly both with client-side routing and a non-root public URL.
21+
Learn how to configure a non-root public URL by running `npm run build`.
22+
-->
23+
<title>React App</title>
24+
</head>
25+
26+
<body>
27+
<noscript>
28+
You need to enable JavaScript to run this app.
29+
</noscript>
30+
<div id="root"></div>
31+
<!--
32+
This HTML file is a template.
33+
If you open it directly in the browser, you will see an empty page.
34+
35+
You can add webfonts, meta tags, or analytics to this file.
36+
The build step will place the bundled scripts into the <body> tag.
37+
38+
To begin the development, run `npm start` or `yarn start`.
39+
To create a production bundle, use `npm run build` or `yarn build`.
40+
-->
41+
</body>
42+
43+
</html>

src/index.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React, { useState, useRef, useEffect, useReducer } from "react"
2+
import ReactDOM from "react-dom"
3+
import useInterval from './useInterval'
4+
5+
import "./styles.css"
6+
7+
function HookApp() {
8+
const [count, setCount] = useState(0)
9+
const handleAdd = () => {
10+
setCount(count + 1)
11+
}
12+
const handleAlert = () => {
13+
setTimeout(() => {
14+
window.alert(count)
15+
}, 2000)
16+
}
17+
return (
18+
<div className="App">
19+
<h1>Hook</h1>
20+
<h1>{count}</h1>
21+
<button onClick={handleAdd}>add</button>
22+
<button onClick={handleAlert}>alert</button>
23+
</div>
24+
)
25+
}
26+
27+
class ClassApp extends React.Component {
28+
state = {
29+
count: 0
30+
};
31+
32+
handleAdd = () => {
33+
this.setState({
34+
count: this.state.count + 1
35+
});
36+
};
37+
38+
handleAlert = () => {
39+
setTimeout(() => {
40+
window.alert(this.state.count);
41+
}, 2000);
42+
};
43+
44+
render() {
45+
return (
46+
<div className="App">
47+
<h1>Class</h1>
48+
<h1>{this.state.count}</h1>
49+
<button onClick={this.handleAdd}>add</button>
50+
<button onClick={this.handleAlert}>alert</button>
51+
</div>
52+
);
53+
}
54+
}
55+
56+
/* --------------- 分割线 --------------- */
57+
58+
function Demo() {
59+
const [count, setCount] = React.useState(0)
60+
61+
useEffect(() => {
62+
const id = setInterval(() => {
63+
setCount(count + 1)
64+
}, 2000)
65+
66+
return () => {
67+
clearInterval(id)
68+
}
69+
}, [])
70+
71+
return <div>Count: {count}</div>
72+
}
73+
74+
function View() {
75+
return (
76+
<React.Fragment>
77+
<HookApp />
78+
<hr />
79+
<ClassApp />
80+
<hr />
81+
{/* <Demo /> */}
82+
</React.Fragment>
83+
);
84+
}
85+
86+
const rootElement = document.getElementById("root");
87+
ReactDOM.render(<View />, rootElement);

src/styles.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.App {
2+
font-family: sans-serif;
3+
text-align: center;
4+
}
5+
6+
button {
7+
padding: 10px 20px;
8+
margin: 10px;
9+
}

src/useInterval.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React, { useState, useRef, useEffect } from "react"
2+
3+
export default function useInterval(callback, delay) {
4+
const cbRef = useRef({})
5+
useEffect(() => {
6+
cbRef.current = callback
7+
}, [callback])
8+
useEffect(() => {
9+
const id = setInterval(() => {
10+
cbRef.current()
11+
}, delay)
12+
13+
return () => clearInterval(id)
14+
}, [delay])
15+
}

0 commit comments

Comments
 (0)