useState 状态钩子

  • useState()给函数式组件引入状态 state ,纯函数不能有状态,所以状态放在钩子里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//引入钩子组件
import React, { useState } from "react";

export default function Button() {
//用钩子引入state状态
//数组的第一个成员是一个变量,指向状态初始值,第二个值是一个函数,用来更新状态,约定是
//set前缀加上状态的变量名
const [buttonText, setButtonText] = useState("Click me, please");
//改变state状态
function handleClick() {
return setButtonText("Thanks, been clicked!");
}
return <button onClick={handleClick}>{buttonText}</button>;
}

useContext 共享状态钩子

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, { useContext } from "react";
import ReactDOM from "react-dom";
//创建一个Context
const AppContext = React.createContext({});

const Navbar = () => {
//子组件通过useContext() 钩子用来引入Context对象,从中获取username属性
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
};

const Messages = () => {
const { username } = useContext(AppContext);
return (
<div className="messages">
<h1>Messages</h1>
<p>1 message for {username}</p>
<p className="message">useContext is awesome!</p>
</div>
);
};

//使用AppContext.Provider 提供一个Context对象,这个对象可以被组建共享
function App() {
return (
<AppContext.Provider
value={{
username: "superawesome",
}}
>
<div className="App">
<Navbar />
<Messages />
</div>
</AppContext.Provider>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

userReducer(): action 钩子

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
import React, { useReducer } from "react";
import ReactDOM from "react-dom";

//Reducer修改state返回新的state
const myReducer = (state, action) => {
switch (action.type) {
case "countUp":
return {
...state,
count: state.count + 1,
};
default:
return state;
}
};

function App() {
//数组的第一个成员是当前状态,第二个成员是发送action的dispatch函数
const [state, dispatch] = useReducer(myReducer, { count: 0 });

return (
<div className="App">
<button onClick={() => dispatch({ type: "countUp" })}>+1</button>
<p>Count: {state.count}</p>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

由于 Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel),如果你需要这两个功能,还是要用 Redux。

useEffect 副作用钩子

  • 用来引入有副作用的操作,最常见的就是向服务器请求数据,之前放在 componentDidMount 里面的代码,现在可以放在这里
  • useEffect 接收两个参数,第一个是函数,一步的操作代码放在里面,第二个是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行,第二个参数省略,每次组件渲染时,就会执行 useRffect
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
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
//用来执行有副作用的操作
useEffect(
() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then((response) => response.json())
.then((data) => {
setPerson(data);
setLoading(false);
});
},
//第二个参数为数组,依赖的参数发生变化就会执行useEffect
[personId]
);

if (loading === true) {
return <p>Loading ...</p>;
}

return (
<div>
<p>You're viewing: {person.name}</p>
<p>Height: {person.height}</p>
<p>Mass: {person.mass}</p>
</div>
);
};

function App() {
const [show, setShow] = useState("1");

return (
<div className="App">
<Person personId={show} />
<div>
Show:
<button onClick={() => setShow("1")}>Luke</button>
<button onClick={() => setShow("2")}>C-3PO</button>
</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useMemo 防止子组件重复执行

父组件状态发生变化,子组件也会重新执行一次 (对应类组件的 componentWillReciveProps,shouldComponentUpdate)
要防止子组件方法调用,我们可以使用 useMemo 来取消没有必要的重新计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useState } from 'react';
import Children from './comp2';

export const ShareContext: any = createContext({});

function SecondHand() {
const [name, setName] = useState('名称');
const [content, setContent] = useState('内容');
return (
<div>
<button onClick={() => setName(new Date().getTime())}>name</button>
<button onClick={() => setContent(new Date().getTime())}>
content
</button>
<Children name={name} content={content}></Children>
</ShareContext.Provider>
</div>
);
}
export default SecondHand;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useMemo } from "react";

export default function Children({ name, content }: any) {
const curName = function () {
console.log("curName is call");
return name + " 最新时间";
};
const curContent = function () {
console.log("curContent is call");
return content + " 最新时间";
};
const cur1 = useMemo(() => curName(), [name]);
const cur2 = useMemo(() => curContent(), [content]);
return (
<div>
<div>content:{cur1}</div>
<div>content:{cur2}</div>
</div>
);
}

通过 useMemo 就可以实现,点击 name,子组件只重绘 name,点击 content,子组件只重绘 content,减少了页面没有必要的页面重绘,用来提升组件性能,与类组件 shouldComponentUpdate 达到的效果一样。

useRef

useRef 可以保存组件节点、变量
react 获得组件节点方式:

  • ref=”” 通过 this.refs 调用
  • ref={(ref)=>this.btnRef=ref}
  • this.btfRef = React.createRef() ref={this.btnRef}

上面三种方式是类组件获得组件实例的方式,或多或少都用到来 this,在函数式组件并不适用

1
2
3
4
5
6
7
8
9
10
import { useRef } from "react";

function Component() {
const btnRef = useRef(null);
return (
<div>
<button ref="btnRef"></button>
</div>
);
}

使用 useContext、useReducer 封装一个类似于 redux 仓库

  • 第一步 创建 store.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createContext } from "react";
//定义共享数据
export const Context = createContext(null);
//定义仓库初始值
export const defayltState = {
count: 0,
};
//定义修改数据方法
export default (state, action) => {
switch (action.type) {
case "add":
return {
...state,
count: state.count + 1,
};
default:
return state;
}
};
  • 第二步 父组件挂载 context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** @format */

import React, { useState, useReducer } from "react";
import Son from "./son";
//引入配置
import myReducer, { defayltState, Context } from "./store";
export default function Father() {
const [state, dispatch] = useReducer(myReducer, defayltState);
//调用后数组第一个成员是当前状态,第二个是发送action的dispatch函数
return (
//通过context 挂载共享属性
<Context.Provider value={{ ...state, dispatch }}>
<h1>{state.count}</h1>
<button onClick={() => dispatch({ type: "add" })}>add 1</button>
<Son></Son>
</Context.Provider>
);
}
  • 第三步 在子组件调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useContext, useEffect } from "react";
import { Context } from "./store"; //引入context

function Son() {
const con = useContext(Context); //拿到数据
useEffect(() => {
setTimeout(() => {
console.log("200");
}, 1000);
}, []); //数组为空只有初始化页面执行,不传递参数每次数据更改都会执行
return (
<div>
this is son---{con.count}
<button onClick={() => con.dispatch({ type: "add" })}>子组件 add1</button>
</div>
);
}
export default Son;

useCallback

React.memo

在类组件中数据发生变化,子组件依赖 props 监听到依赖,就算值一样也会发生 re-render(重绘)

  • shouldComponentUpdate+componentWillRecieveProps 来判断
  • React.PureComponent 来定义类

在函数式组件中,我们可以通过 React.memo()来优化组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";

// 普通的函数式组件
function Child({ seconds }) {
console.log("I am rendering");
return <div>I am update every {seconds} seconds</div>;
}

// 类似shouldComponentUpdate 判断参数,是否进行重绘
// return true 重绘
// return false 不重绘
function areEqual(prevProps, nextProps) {
if (prevProps.seconds === nextProps.seconds) {
return true;
} else {
return false;
}
}
// React.memo(函数式组件,[依赖判断函数])
export default React.memo(Child, areEqual);