1.useState
使用状态
1
2
3
|
const [n, setN] = React.useState(0)
const [user, setUser] = React.useState({name:'F'})
|
注意事项1:不可局部更新
1
2
3
4
5
|
const [user, setUser] = useState({name:'Jack',age:18})
setState({
name:'Sam'
})
|
-
结果得到的是{name:‘Sam’},age属性消失了
-
(可以在之前加上…user,表示复制user的全部属性到这个新对象中)
-
说明对于state不能部分setState,因为setState不会帮我们合并属性
-
实际上React中所有hooks都不能自动合并属性,需要我们自己去操作
注意事项2:地址要变
setState(obj)如果obj地址不变,那么React就认为数据没有变化
1
2
3
4
5
|
const [user, setUser] = useState({name:'Jack',age:18})
user.name = 'Sam'
setState(user)
|
- 表面上我们先改变了user对象的name属性(实际上也确实改变了),再将user对象setState进去,setState之后的user对象并没有改变。
- 对于React来说,对象还是user(地址没变),因此React不认为user改变了,依然加载的是原来的user。
- 上一个例子,是在setState中创建了一个新对象,因此React会直接加载新的对象(这也反映了setState并不会直接修改state,而是生成一个新的state)
useState接受函数
1
2
3
|
const [state, setState] = useState(() => {
return initState
})
|
该函数返回初始state,且只执行一次,节约反复更新渲染组件时js引擎的解析时间(首次渲染时会解析,再次更新组件时js就不会解析这个匿名函数了)
setState接受函数
1
2
3
4
5
|
const [n, setN] = useState(0)
setN(n + 1)
setN(n + 1)
|
虽然运行了两次setN,但运行结果为1,并不是预想中的2,原因同样是由于setN并不会改变n的值,n始终为0
传入函数的方式并没有直接传入state进行计算,仅仅是一个形式化的操作而已,建议优先选用此种方式进行setState
2.useReducer
(用来践行Flux/Redux的思想)
1.创建初始值initialState
1
|
const initial = { n = 0 }
|
2.创建所有操作reducer(state,action)
1
2
3
4
5
6
7
8
9
|
const reducer = (state, action) =>{//旧状态,操作的类型
if(action.type === 'add'){
return { n: state.n+action.number}
}else if(action.type === 'mult'){
return { n: state.n*2 }
}else{
throw new Error('unknown type')
}
}
|
3.传给useReducer,得到读和写API,调用写({type : ‘操作类型’ })
1
2
3
4
5
6
7
|
function App(){
const [state, dispatch] = useReducer(reducer,initial)
const onClick = ()=>{
dispatch({type: 'add', number : 2})//参数为action对象
}
return ...
}
|
4.总的来说,useReducer是useState的复杂版
3.useContext
1.上下文是局部的全局变量
2.使用方法
1.使用C = createContext(initial)创建上下文
1
|
const C = createContext(null)
|
2.使用<C.Provider/>圈定作用域
1
2
3
4
5
|
const [n, setN] = useState(0)
<C.Provider value={{n, setN}}>//将state和setState作为对象值传入上下文C中
<App/>
</C.Provider>
|
3.在作用域内使用useContext(C)来使用上下文
1
2
3
|
const {n, setN} = useContext(C)
onClick = () =>{setN(n => n + 1)}
|
3.注意事项
- 不是响应式的
- 在一个模块中将C里面的值改变,另一个模块不会感知到这个变化
4.useEffect
1.副作用
实际上叫做afterRender更好,每次render后运行
(对环境的改变即为副作用,如修改document.title
但我们不一定非要把副作用放在useEffect里
2.用途
1.替代componentDidMount使用,[ ]作为第二个参数,表示除第一次渲染后执行外,之后不会再执行
1
2
3
|
useEffect(() =>{
console.log('第一次渲染后执行')
},[])
|
当第二个参数为空时,表示组件的任何状态变化后都会执行,包括第一次渲染后
2.替代componentDidUpdate使用,可指定依赖,当n变化是执行,包括第一次渲染后
1
2
3
|
useEffect(() =>{
console.log('n变化后执行')
},[n])
|
3.替代componentWillUnmount使用,通过return
1
2
3
4
5
6
|
useEffect(() =>{
const id = setInterval(() =>{
console.log('hi')
},2000)
return () =>{window.clearInterval(id)}
},[])
|
以上三种用途可同时存在
3.特点
如果同时存在多个useEffect,会按照出现次序执行
5.useLayoutEffect
布局副作用
会在浏览器渲染前(屏幕像素改变之前)执行
特点
经验
为了用户体验,优先使用useEffect(先将画面渲染给用户,再进行副作用)
6.useMemo
- React中默认有多余的render,导致依赖其他状态的无关组件会被再次执行渲染
可以使用React.memo(Child)将无关组件进行封装
1
|
const Child2 = React.memo(Child)
|
- Bug:但如果Child的props中有接收到的是函数时,但随着父组件的执行渲染,函数表达虽然没变,但此时地址改变了,故仍被再次执行,导致Child仍再次渲染
使用useMemo封装此函数解决这个问题:
1
2
3
|
const onClickChild = useMemo(() =>{
return () =>{ ...}
},[m])
|
第一个参数是()=>value
第二个参数是依赖[m,n]
只有当依赖变化时,才会计算出新的value;如果依赖不变,那就重用之前的value
与Vue2中的computed相似
一般先memo组件,再useMemo函数
7.useCallBack
如果value是个函数,useMemo就要写成:
1
|
useMemo( () = > (x) =>{console.log(x) } )
|
写起来很复杂,因此使用useCallback解决
1
|
const onClickChild = useCallback ( (x) = > { log(x) }, [m] )
|
8.useRef
1
|
const count = useRef(0)
|
- 也可以将ref作为属性绑定到元素上,使用此ref能够引用到组件对应的DOM对象,类似document.getElementById
9.forwardRef
由于props无法传递ref属性,导致不能直接在子组件上定义ref
可以使用forwarRef将组件封装成新组件
1
2
3
|
const Button2 = React.forwardRef((props,ref)=>{
return <button ref={ref} {...props}/>
})
|
实际上只是允许了子组件接受一个新参数ref
10.替代Redux
1.将数据集中在store对象
1
2
3
4
5
|
const store = {
user:null,
books:null,
movies:null
}
|
2.将所有操作集中在reducer
1
2
3
4
5
6
7
8
9
10
11
12
|
const reducer = (state, action) => {
switch (action.type) {
case "setUser":
return { ...state, user: action.user };
case "setBooks":
return { ...state, books: action.books };
case "setMovies":
return { ...state, movies: action.movies };
default:
throw new Error();
}
};
|
3.创建Context
1
|
const Context = React.createContext(null)
|
4.创建对数据的读写API(只能写在函数组件中)
1
|
const [state, dispatch] = useReducer(reducer, store)
|
5.将第四步的内容放到第三步的Context(把读写API赋值给Context.Provider)
1
2
3
|
<Context.Provider value={{state,dispatch}}>
...
</Context.Provider>
|
6.用Context.Provider将Context提供给所有组件(包起来)
1
2
3
4
5
|
<Context.Provider value={{state,dispatch}}>
<User/>
<Books/>
<Movies/>
</Context.Provider>
|
7.各个组件用useContext获取读写API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function User() {
const { state, dispatch } = useContext(Context);
useEffect(() => {
ajax("/user").then(user => {
dispatch({ type: "setUser", user: user });
});
}, [ ]);
return (
<div>
<h1>个人信息</h1>
<div>name: {state.user ? state.user.name : ""}</div>
</div>
)
}
|
8.模块化

11.自定义hooks
index.jsx:
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
|
import React from "react";
import ReactDOM from "react-dom";
import useList from "./hooks/useList";
function App() {
const { list, deleteIndex, addItem } = useList();
return (
<div className="App">
<h1>List</h1>
<button
type="submit"
onClick={() => {
addItem("jack");
}}
>
添加
</button>
{list ? (
<ol>
{list.map((item, index) => (
<li key={item.id}>
{item.name}
<button
onClick={() => {
deleteIndex(index);
}}
>
删除
</button>
</li>
))}
</ol>
) : (
"加载中..."
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
|
hooks/useList.js:
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
|
import { useState, useEffect } from "react";
const useList = () => {
const [list, setList] = useState(null);
useEffect(() => {
ajax("/list").then((list) => {
setList(list);
});
}, []); // [] 确保只在第一次运行
return {
list: list,
addItem: (name) => {
setList([...list, { id: Math.random(), name: name }]);
//先复制一遍之前的list,再z后面写一个新的对象即可
},
deleteIndex: (index) => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
//截取从开头到被删对象的部分数组(不包括被删对象)
//截取从被删对象下一个开始到最后的部分数组
//通过concat将两个数组合并
}
};
};
export default useList;
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ id: "1", name: "Frank" },
{ id: "2", name: "Jack" },
{ id: "3", name: "Alice" },
{ id: "4", name: "Bob" }
]);
}, 2000);
});
}
|