React 脚手架和 hook
# 脚手架
react 也为我们提供了官方的脚手架工具,可以帮助我们快速搭建项目。
npx create-react-app <项目名>
接下来,我们来看一下目录结构:
- .gitignore:忽略文件的配置文件
- node_modules:依赖包
- package.json:依赖配置以及启动脚本
- public:静态文件,入口 html
- src:源码目录
- App.js :App 组件
- index.js:入口 js 文件
课堂练习:脚手架版本的《待办事项》详细参见源码
# hook
我们知道,在react 中,组件分为两种,函数组件和类组件。
以前,函数组件是没有状态,所以被称之为无状态组件,而类组件是有状态,所以类组件被称之为有状态组件。
最新的react 引入了 hook,他可以让我们的函数组件也拥有状态。
- 比起类,前端开发人员更加熟悉函数
- 比起类,函数没有烦人的 this
- 函数组件写起来比类组件更加简洁
- 类组件更重,函数组件更轻
class App extends React.Component{}
在 react 中,提供了一组内置的 hook
- useState
- useEffect
- useContext
- useCallback
- useMemo
- useRef
useState 可以让我们在函数组件内部维护一组数据。
import React, { useState } from 'react'
export default function App() {
let [count, setCount] = useState(0);
return (
<div>
<p>你点击了{count}次</p>
<button onClick={()=>setCount(++count)}>+1</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
通过上面的例子,我们可以看到,我们使用 useState 在我们的函数组件里面维护了数据。
useState 返回两个东西,一个是初始值,一个设置这个值的方法。
可以使用 useState 来维护多组数据
import React, { useState } from 'react'
export default function App() {
let [count, setCount] = useState(0);
let [fruit, setFruit] = useState('苹果');
let [stu, setStu] = useState({
name: 'qingqing',
age: 18,
gender: 'female'
})
let obj = {...stu};
obj.name = 'shasha'
return (
<div>
<p>你点击了{count}次</p>
<button onClick={() => setCount(++count)}>+1</button>
<p>{fruit}</p>
<button onClick={() => setFruit('香蕉')}>改成香蕉</button>
<ul>
<li>name : {stu.name}</li>
<li>age : {stu.age}</li>
<li>gender : {stu.gender}</li>
</ul>
<button onClick={() => setStu(obj)}>修改</button>
</div>
)
}
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
useEffect :可以处理副作用的 hook
这里涉及到了纯函数和不纯函数的区别。来自于函数式编程的思想。
我们编程的范式分为两大类:
命令式编程(简单):我来帮助计算机想好怎么做,计算机只需要执行(How)
- 面向过程
- 面向对象
声明式编程(复杂):我只需要告诉计算我要什么,具体的实现我不管(What)
- DSL(领域专用语言)
- 函数式编程
什么是纯函数?
所谓纯函数,就是指一个输入有一个可以预期的输出。
function test(x){
return x + 1;
}
// 1---->2
// 100 ---->101
// 根据我的输入,可以预测我的输出
2
3
4
5
6
不纯的函数,函数里面有副作用,你不能够预测函数调用后的结果
// 读取文件、发送网络请求
function test(x){
// 发送网络请求
}
2
3
4
useEffect 示例
import React, { useState, useEffect } from 'react'
export default function App() {
const [count,setCount] = useState(0);
useEffect(()=>{
document.title = `你点击了${count}次`
})
return (
<div>
<p>你点击了{count}次!</p>
<button onClick = {()=>setCount(count+1)}>+1</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
当 react 要渲染我们的组件的时候,它会先记住我们要用到的副作用,之后每一次重新渲染,都会调用这个副作用的钩子函数。
在开发中,有些时候,在副作用里面注册了一个计时器(setInterval),回头组件在销毁的时候,就需要将这个计时器清除掉,可以使用 useEffect 返回一个函数。
返回的这个函数会在下一次渲染之后执行。
import React, { useState, useEffect } from 'react'
export default function App() {
const [count,setCount] = useState(0);
useEffect(()=>{
document.title = `你点击了${count}次`;
console.log('执行了副作用函数');
return function cleanup(){
// 返回的这个函数一般就是做副作用的清理工作的
console.log('在下一次渲染之后执行');
}
})
return (
<div>
<p>你点击了{count}次!</p>
<button onClick = {()=>setCount(count+1)}>+1</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在上面的例子中,我们可以看到,返回的函数在下一次渲染视图之后,副作用函数执行之前的时机进行执行。
useEffect 还可以设置依赖项。
现在每一次视图更新,就会重新执行副作用函数,我们可以设置以来项目,第二个参数就是依赖项目。
import React, { useState, useEffect } from 'react'
export default function App() {
const [count1,setCount1] = useState(0);
const [count2,setCount2] = useState(0);
const [count3,setCount3] = useState(0);
useEffect(()=>{
console.log('执行了副作用函数')
},[count1,count3])
return (
<div>
<p>Count1:你点击了{count1}次!</p>
<p>Count2:你点击了{count2}次!</p>
<p>Count3:你点击了{count3}次!</p>
<button onClick = {()=>setCount1(count1+1)}>+1</button>
<button onClick = {()=>setCount2(count2+1)}>+1</button>
<button onClick = {()=>setCount3(count3+1)}>+1</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
useContext
可以做一个传递数据的方法,从而避免了每一个层级都要手动传递 props 数据
// App.jsx
import React, { useState, useEffect } from 'react'
import Parent from './components/Parent'
// 创建一个上下文组件
export const demo = React.createContext();
export default function App() {
return (
// Provider 代表数据的生产者,负责提供数据
<demo.Provider value={{name:'qingqing',age:18}}>
<div>
<Parent />
</div>
</demo.Provider>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useContext } from 'react'
import {demo} from '../App'
export default function Child1() {
const { name, age } = useContext(demo);
return (
<div>
这是子组件1
<p>name:{name}</p>
<p>age:{age}</p>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
useCallback
主要是做性能优化方面。
先来看下面的例子:
// App.jsx
import React from 'react'
import Parent from './components/Parent'
export default function App() {
return (
<div>
<h1>这是 App 组件</h1>
<Parent />
</div>
)
}
// Parent.jsx
import React, { useState } from 'react';
import Child1 from './Child1'
import Child2 from './Child2'
const Parent = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const test1 = ()=>{
setCount1(count1+1);
}
const test2 = ()=>{
setCount2(count2+1);
}
console.log('父组件渲染了');
return (
<div>
<p>这是父组件</p>
<p>父组件数据1:{count1}</p>
<p>父组件数据2:{count2}</p>
<Child1 click={test1}/>
<Child2 click={test2}/>
</div>
);
}
export default Parent;
// Child1.jsx
import React from 'react'
export default function Child1(props) {
console.log('子组件1渲染了');
return (
<button onClick={props.click}>+1</button>
)
}
// Child.jsx
import React from 'react'
export default function Child2(props) {
console.log('子组件2渲染了');
return (
<button onClick={props.click}>+1</button>
)
}
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
通过上面的例子,我们可以看出,即使子组件2没有关系,但是也会重新进行渲染。
究其原因,是因为父组件重新进行渲染的时候,那两个函数也重新生成了,既然重新生成了,所以就会向两个子组件重新传值,那么也就必然导致两个子组件都重新进行渲染。
所以这个时候,就需要 useCallback。
useCallback 可以让方法进行一个缓存,只有依赖的数据发生变化时,才会重新生成。
注意,useCallback 要和 React.memo 联合使用,React.memo 将要缓存的子组件包裹起来即可。
// Parent.jsx
const test1 = useCallback(()=>{
setCount1(count1+1);
},[count1]);
const test2 = useCallback(()=>{
setCount2(count2+1);
},[count2]);
// Child1.jsx
import React from 'react'
export default React.memo(function Child1(props) {
console.log('子组件1渲染了');
return (
<button onClick={props.click}>+1</button>
)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
useMemo
类似于 useCallback,也是处理性能相关的。类似于 vue 的计算属性。
// Parent.jsx
import React, { useState,useMemo } from 'react';
const Parent = () => {
const [count,setCount] = useState(0);
const [val,setVal] = useState('');
// 这个就类似于vue 中的计算属性
const demo = useMemo(function getNum(){
console.log('调用了');
return count;
},[count]);
return (
<div>
<h1>count:{demo}</h1>
<div>
<button onClick={()=>setCount(count+1)}>+1</button>
<input type="text" name="" value={val} onChange={(e)=>setVal(e.target.value)} id=""/>
</div>
</div>
)
}
export default Parent;
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
接下来我们来看一下它和 callBack 的区别
共同点:仅仅在依赖数据发生变化的时候,才会重新计算结果,起到了一个缓存的作用。
两者区别:
- useMemo 最终会返回一个值,缓存的是那个值
- useCallback 计算结果是一个函数,主要用于缓存函数
useRef
前面我们学习过 createRef 方法,通过这个方法,可以创建一个 ref,用这个 ref 和表单的控件关联在一起。然后就可以获取到这个控件。
useRef 就是 createRef 的一个升级版。
import React, { useState,useRef } from 'react';
const Parent = () => {
const ele1 = React.createRef();
// 如果是通过 createRef 创建的 ref,那么每一次组件重新渲染的时候,都会重新进行创建
const ele2 = useRef();
// 但是,如果是使用 useRef 创建的ref,会有一个缓存,组件重新渲染的时候,不会创建新的 ref
const [count,setCount] = useState(0);
console.log(ele1);
console.log(ele2);
if(ele1.current){
console.log('ele1 已经有值了');
}
if(ele2.current){
console.log('ele2 已经有值了');
}
return (
<div>
<input type="text" name="" id="" ref={ele1}/>
<input type="text" name="" id="" ref={ele2}/>
<button onClick={()=>setCount(count+1)}>+1</button>
</div>
)
}
export default Parent;
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