-
Notifications
You must be signed in to change notification settings - Fork 0
/
reacthooks笔记.txt
704 lines (588 loc) · 23.3 KB
/
reacthooks笔记.txt
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
一.react类声明组件的缺点
难以复用的状态逻辑
难以捉摸的声明周期
混乱的副作用
this指向困扰:内联函数会创建新的句柄,导致组件重新渲染,不保证this的指向。
二、react的安装
除了react和react-dom之外,其他模块都封装在react-script中了
可以使用npm run eject解构编译脚本,使用前要git add . 保存代码。
三、react最新特性简介及context的使用
3.1 Context--->会向上查找。
定义:提供了一种方法,能够让数据跨层级传递,并且变化是响应式的
派生出两个:Provider 和 consumer
如何创建context:
const ThemeContext = React.createContext('light'); // 在Consumer找不到provider就会取这个值。
import React,{Component,createContext} from 'react';
import './App.css';
const BatteryContext = createContext('light');
// 子组件可以接受一个context参数
class Leaf extends Component {
render() {
return (
<div>
<BatteryContext.Consumer>
{(context)=>context}
</BatteryContext.Consumer>
</div>
);
}
}
// 中间组件,为了营造多层级
class Middle extends Component {
render() {
return <Leaf/>;
}
}
// 父级组件
function App() {
return (
<div className="App">
<BatteryContext.Provider value={60}>
<Middle/>
</BatteryContext.Provider >
</div>
);
}
export default App;
如果有多个要传递的值,只需要多创建一个createContext,然后嵌套使用就行了。
<BatteryContext.Provider value={60}>
<BatteryContext2.Provider value={80}>
<Middle/>
</BatteryContext2.Provider >
</BatteryContext.Provider >
使用
<BatteryContext.Consumer>
{(context)=><BatteryContext2.Consumer>{value2=>value2}</BatteryContext2.Consumer>}
</BatteryContext.Consumer>
使用contextType美化我们写法
static contextType = BatteryContext; //指定 contextType 读取当前的context。
render() {
return (
<div>
{this.context}
</div>
);
}
*Tips:上面的写法是写在一个文件中的,如果父子组件不在一个文件中,要共享context,就要把createContext的方法抽离出来。、
父组件
import React,{useState} from 'react';
import Bar from './Bar'
import {CountContext} from './util'
import './App.css';
function App(props) {
const [count, setCount] = useState(0);
return (
<div>
<CountContext.Provider value={count}>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<Bar value={count}/>
</CountContext.Provider>
</div>
);
}
export default App;
子组件:
import { CountContext } from '@/utils/context';
const getProviderValue=()=>{
return <CountContext.Consumer>{value=><span>{value}</span>}</CountContext.Consumer>
}
const Child=props=>{
return (
getProviderValue()
);
}
3.2 Lazy与Suspense实现延迟加载
import React,{lazy,Suspense}from 'react';
import './App.css';
const About =lazy(()=>import(/*webpackChunkName:"about"*/'./About.js')); //webpack允许我们自定义包名。
function App() {
return (
<div className="App">
<Suspense fallback={<div>loading</div>}>
<About/>
</Suspense>
</div>
);
}
export default App;
错误边界:捕获加载错误
加载错误时会触发componentDidCatch生命周期函数,在这里面我们可以进行一些操作。
更简便的是
state = { hasError: false };
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
3.3 PureComponent和memo
使用PureComponent需要注意的是
a.Pure只会浅层比较,如果第一层没有发生变化,而里面发生变化了,可能引起视图不更新的bug。
b.渲染子组件的绑定一个内联函数的时候,每次都会渲染(因为子组件每次相当于都传了一个新new的方法),我们要把这个方法变成属性,来解决这个问题。
使用memo是pureComponent一样
import React,{Component,memo} from 'react';
import './App.css';
//包裹组件,count更新时触发render,age更新的时候无法更新
const Foo=memo(function Foo(props){
console.log('render....')
return <div>{props.conunt}</div>
});
class App extends Component{
state={
conunt:1,
person:{
age:10
}
};
render() {
let {person,conunt} = this.state;
conunt++;
return (
<div className="App">
<button onClick={
()=>this.setState({conunt:conunt})
}>click</button>
<Foo person={person} conunt={conunt}/>
</div>
);
}
}
export default App;
如果要深入比较,要传入第二个参数:一个比较函数
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual)
四.Hooks
定义:不编写 class 的情况下使用 state 以及其他的 React 特性
4.1 state Hooks
function App() {
// 声明一个新的叫做 “count” 的 state 变量
const [count, setCount] = useState(0); // 返回两个参数,第一是这个变量,第二个是设置这个变量的函数。
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
几个原则:
1.useState可以有多个
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
2.每次渲染的时候,必须是相同的数量相同的个数,不然react会报错
为了规范我们的书写,可以用eslint-plugin-react-hooks来规范(安装后如果报错,运行npm i 或者再npm add @babel/runtime)
参考:https://react-1251415695.cos-website.ap-chengdu.myqcloud.com/docs/hooks-rules.html
3.useState允许我们传入一个函数,用于state的延迟初始化,这个函数只会执行一次。
如果你的初始化逻辑很复杂,这是一个不错的优化方法。
const [count, setCount] = useState(()=>{
console.log('init'); // 这个箭头函数只会执行一次,就是在App初始化的时候。
return props.defaultCount||0
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
4.2 useEffect
告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。
//与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。
//大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
1.不需需要清除的副作用(Effect)
// 就像 componentDidMount and componentDidUpdate:
useEffect(() => {
document.title = `You clicked ${count} times`;
});
2.需要清除的副作用:
原理: 每个 effect 都可以返回一个清除函数
React 会在执行当前 effect 之前对上一个 effect 进行清除。
所以如果这个effect仅仅是在组件渲染的时候调用一次,那么也只会在组件销毁时执行清除操作(执行回调函数),相当于componentWillUnmount。
当然effect的可以多次调用,执行当前 effect 之前对上一个 effect 进行清除。
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
3.使用多个Effect实现关注点分离
useEffect(() => {
document.title = `You clicked ${count} times`;
});
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
});
4.通过跳过 Effect 进行性能优化
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
*Tips:
当数组内的所有元素都不发生变化,useEffect才不会重新执行,否则都会重新执行
如果不传第二个参数,每次都会执行。
4.3 useContext --->关于父子组件的拆分,可以看上面的context部分
import React,{useState,useContext,createContext} from 'react';
import './App.css';
const CountContext = createContext(0);
function Bar(){
const count = useContext(CountContext);
return (
<div>{count}</div>
)
}
function App(props) {
const [count, setCount] = useState(0);
return (
<div>
<CountContext.Provider value={count}>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<Bar/>
</CountContext.Provider>
</div>
);
}
export default App;
4.4 useMemo和useCallback
useMemo定义一段函数是否需要重复执行(仅仅用来做性能优化,不应该改变代码逻辑)
与useEffect的区别:
相同:执行策略和useEffect相同
useEffect是执行副作用的,是在渲染之后执行的。
useMemo是在渲染时候执行的,参与渲染。
function App() {
const [count, setCount] = useState(0);
const double =useMemo(()=>{
return count*2
},[count===3]); // 返回的是一个布尔值,在等于3之前一直是false,不会重新计算,=3变成true重新计算,>3变成false,重新计算,之后一直是false,不重新计算了。
return (
<div>
<p>You clicked {count} times</p>
<h1> {double}</h1>
<Counter count={count}/>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
优化传入子组件的函数句柄
mport React,{useState,useMemo,memo} from 'react';
import './App.css';
const Counter = memo(function Counter(props){
console.log('counter render..'); // 即使double没有变化,也会答应,因为传入的onclick每次都是传入新的一个句柄
return (
<h1 onClick={props.onClick}>{props.count}</h1>
)
});
function App() {
const [count, setCount] = useState(0);
const double =useMemo(()=>{
return count*2
},[count===3]);
const onClick=()=>{
console.log('click')
};
return (
<div>
<p>You clicked {count} times</p>
<Counter count={double} onClick={onClick}/>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
===>修改
import React,{useState,useMemo,memo} from 'react';
import './App.css';
const Counter = memo(function Counter(props){
console.log('counter render..');
return (
<h1 onClick={props.onClick}>{props.count}</h1>
)
});
function App() {
const [count, setCount] = useState(0);
const double =useMemo(()=>{
return count*2
},[count===3]);
const onClick=useMemo(()=>{
return ()=>{
console.log('click')
}
},[]); // 由于传入的是一个空数组,这里只会执行一次
// **useCallback是useMemo的变种
const onClick=useCallback(()=>{
console.log('click')
},[]); // 一般不是空数组,一般是有依赖的。
return (
<div>
<p>You clicked {count} times</p>
<Counter count={double} onClick={onClick}/>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default App;
**Tips,在useMemo的策略中不需要加入setState的函数,因为react可以保证每次返回的setState函数都是同样一个句柄。
setCount可以获取当前的值 setCount((count)=>count+1)
4.5 useRef
两种使用场景:
1.获取dom元素
2.保存可变值
获取dom元素的值
父组件中
const countRef = useRef();
const onClick = useCallback(() => {
console.log(countRef.current)
countRef.current.speak() // 获取子组件的成员方法。
}, [countRef]);
<Counter count={double} onClick={onClick} ref={countRef}/>
子组件中:只有类组件才能实例化
class Counter extends PureComponent {
speak(){
console.log('son is speak')
}
render() {
const {props} = this;
return (
<h1 onClick={props.onClick}>{props.count}</h1>
)
}
}
第二种使用场景:
let it;
useEffect(()=>{
it = setInterval(()=>{
setCount((count)=>count+1)
},1000)
},[]);
useEffect(()=>{
if(count>10){
clearInterval(it);
}
});
这种情况下,当count>10的时候,并不会停止更新。因为每次组件重新渲染都会重新产生一个it
改造:
const it = useRef();
useEffect(()=>{
it.current = setInterval(()=>{
setCount((count)=>count+1)
},1000)
},[]);
useEffect(()=>{
if(count>=10){
clearInterval(it.current);
}
});
4.6 自定义hook,
在自定义hook函数内部,仍然可以使用内置hook函数,相当于把这一部分代码抽离出来,方便复用。
// 自定义的hook,以use开头命名
function useCounter(defaultCounter){
const [count, setCount] = useState(defaultCounter);
const it = useRef();
useEffect(()=>{
it.current = setInterval(()=>{
setCount((count)=>count+1)
},1000)
},[]);
useEffect(()=>{
if(count>=10){
clearInterval(it.current);
}
});
return [count,setCount]
}
function App(props) {
const [count, setCount] = useCounter(0);
return (
<div>
<p>You clicked {count} times</p>
<Counter count={count} />
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
自定义hook和函数组件很相似,除了输入输出,几乎无法区别自定义hook和函数组件。
4.7 hook规则
-只在最顶层使用hook
-只在react函数中调用hook(函数组件和自定义hook函数)
4.8 hook常用技巧
https://react-1251415695.cos-website.ap-chengdu.myqcloud.com/docs/hooks-faq.html
生命周期方法要如何对应到 Hook?
constructor:函数组件不需要构造函数。你可以通过调用 useState 来初始化 state。如果计算的代价比较昂贵,你可以传一个函数给 useState。
getDerivedStateFromProps:改为 在渲染时 安排一次更新 setCount。
shouldComponentUpdate:详见 下方 React.memo.
render:这是函数组件体本身。
componentDidMount, componentDidUpdate, componentWillUnmount:useEffect Hook 可以表达所有这些(包括 不那么 常见 的场景)的组合。
componentDidCatch and getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法,但很快会加上。
有类似实例变量的东西吗? 使用useRef你可以在 useEffect 内部对其进行写入:
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
}
如何获取上一轮的 props 或 state?
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
五.redux
5.1 如何推到出redux的api
以todolist的demo为例:
-->我们有add,set,del,toggle操作
-->这些操作都是通过setState来改变state的值,让我们的操作变得无法追踪
-->我们通过构建一个集中处理函数dispatch函数来处理我们的操作,传入{type:'add',payload:payload} payload 是需要用的数据
现在调用 dispatch(action)
-->{type:'add',payload:payload}-->这样的叫做一个action
-->我们每次调用的时候,都需要手动创建一个action,所以我们需要创建一个actionCreator函数来帮我们创建action
creatAdd({payload:payload})
现在调用 dispatch(creatAdd)
-->这时候我们希望进行一层封装,比如函数addTodo 来实现 dispatch(creatAdd)的逻辑,
我们来传入
{
addTodo:createAdd,
removeTodo:createRemove
}
然后通过bindActionCreator来批量转化--->返回的是个对象,value是一个函数
我们传入子组件的就不是dispatch(creatAdd) 而是
{
...bindActionCreator({
addTodo:createAdd,
removeTodo:createRemove
},dispatch)
}
-->当我们的state变得复杂,就需要从数据字段的维度来考虑数据的更新。比如一次set,可能不止set todos,还可能set其他值。
我们创建一个reducer函数
reducer(state,action) ==>返回一个经过action处理后的新的state
然后:设置新的值。
const newState= reducer(state,action)
for(let key in newState){
setters[key](newState(key)) // setters[key]是一个对应的setState函数
}
--->我们需要拆分reducers为多个reducer函数
const reducers ={
todos(state,action){},
increment(state,action){}
}
然后定义一个combineReducers()
--->我们要给action提供一个能力,异步处理
所以我们要求action不单可以返回一个对象,还可以返回一个函数
这个函数执行dispatch,
但是由于异步产生一个问题,因为我们获取到state是之前传进来的旧的state----》在外层创建一个store,每次state变化,都同步到store,然后我们使用store就行了。
六:PWA
6.1 service work
我们新建一个项目,启动一个本地服务器 npm i serve,然后运行serve就行了
index.html
<html>
<body>
222
<script>
navigator.serviceWorker.register('./sw.js',{scope:'/'})
.then(registration =>{
console.log(registration)
},error=>{
console.error(error)
})
</script>
</body>
</html>
sw.js
// 在service work的上下文中不可以使用dom,localstorage等对象
// 通过监听生命周期来进行工作的
// 只要两个sw有一丝丝不同,新的版本会被下载安装,但不会立刻生效
self.addEventListener('install',event=>{
console.log('install',event);
// waitUntil 用于延迟activate的执行。
// event.waitUntil(new Promise(resolve => {
// setTimeout(resolve,5000)
// }));
// event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate',event=>{
console.log('activate',event);
// event.waitUntil(self.clients.claim()) // 后面会讲到
});
// 请求网络
self.addEventListener('fetch',event=>{
console.log('fetch',event)
});
*** cache Api
const CACHE_NAME = 'cache-v2';
self.addEventListener('install',event=>{
event.waitUntil(caches.open(CACHE_NAME).then(cache=>{
cache.addAll([
'/',
'./index.css'
])
}))
});
self.addEventListener('activate',event=>{
console.log('activate',event);
event.waitUntil(caches.keys().then(cacheNames => {
return Promise.all(cacheNames.map(cachName=>{
if(cachName!==CACHE_NAME){
return caches.delete(cachName);
}
}))
}))
});
// 请求网络
self.addEventListener('fetch',event=>{
console.log('fetch',event)
event.respondWith(caches.open(CACHE_NAME).then(cache => {
return cache.match(event.request).then(response =>{
if(response) {
return response
}
return fetch(event.request).then(response =>{
cache.put(event.request,response.clone());
return response;
})
})
}))
});
6.5 notification Api 浏览器通知。--->了解。
6.6 在项目中如何开启pwa:workbox,在create react中已经集成了。
七:[项目篇]火车票业务架构
分文件夹创建4个模块,改造webpack.config.js,以期可以打包多个页面。
要修改的:HtmlWebpackPlugin,entry 如果报错,删除ManifestPlugin的publicPath之后的部分。