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

1
setN(i => i + 1)

传入函数的方式并没有直接传入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

布局副作用

会在浏览器渲染前(屏幕像素改变之前)执行

特点

  • useLayoutEffect总是比useEffect要先执行

  • useLayoutEffect中的任务最好确实影响了Layout

经验

为了用户体验,优先使用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

  • 如果需要一个值,在组件不断render时保持不变,用useRef

  • 初始化:

1
const count = useRef(0)
  • 读取:
1
count.current
  • 也可以将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.模块化

![priceless-jennings-gyls6 - CodeSandbox - Google Chrome 2021_6_2 14_29_21](article_img/priceless-jennings-gyls6 - CodeSandbox - Google Chrome 2021_6_2 14_29_21-1622626575090.png)

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);
  });
}