useState
import React from 'react'export default function App() {const [n, setN] = React.useState(0)const addN = () => {setN(n+1)}return (<div className="App"><div>{n}</div><button onClick={addN}>+1</button></div>);}
注意事项
setState不会自动合并属性
const [user, setUser] = React.useState({name: 'jack', age: 16})setUser({...user, age: 19})
如果state是对象,setState应返回一个新对象,如果obj地址不变, React会认为数据没变化
useState接受函数,好处是减少浏览器解析
const [state, setState] = React.useState(()=>{return initialState})
setState接受函数,setState更新是异步的,传回调函数保证每次setState都能更新
setState(n => n + 1)
useReducer
useReducer践行Flux/Redux思想 ```javascript import React from ‘react’ // 1. 创建初始值initialState const initial = { n: 0 } // 2. 创建所有操作reducer(state, action) const reducer = (state, action) => { if (action.type === ‘add’) { return {n: state.n + action.number} } else if(action.type === ‘multi’) { return {n: state.n * action.number} } else { throw new Error(“unknown type”) } }
export default function App() { // 3. 将reducer和initialState传给useReducer, 得到读和写API const [state, dispatch] = React.useReducer(reducer, initial)
const addN = ()=>{ // 4. 调用写API dispatch({type: ‘ad’, number: 2}) }
return (
<a name="6AESe"></a>### useReducer替代Redux```javascriptimport React, { useEffect } from "react";// 1. 初始化storeconst store = {user: null,books: null,movies: null};// 2. 将所有操作集中在reducerfunction 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. 创建Contextconst Context = React.createContext(null);export default function App() {// 4. 用useState创建读和写的APIconst [state, dispatch] = React.useReducer(reducer, store);const api = { state, dispatch };return (// 5. 通过Context.Provider将第四步的内容传给各组件<Context.Provider value={api}><User /><hr /><Books /><Movies /></Context.Provider>);}function User() {// 6. 各组件通过useContext获取读写APIconst { state, dispatch } = React.useContext(Context);useEffect(() => {ajax("/user").then((user) => {dispatch({ type: "setUser", user: user.name });});}, []);return (<div><h1>个人信息</h1><div>name: {state.user ? state.user : ""}</div></div>);}
完整代码:https://codesandbox.io/s/sweet-feather-iglyi
组件模块化
以上例为例
- 将组件都放到components文件夹里
- 将ajax和Context都放到单独的文件
- 将reducer都放到reducers文件夹

export default {setUser: (state, action) => {return { ...state, user: action.user };}};
const obj = {...userReducer,...booksReducer,...moviesReducer};function reducer(state, action) {const fn = obj[action.type];if (fn) {return fn(state, action);} else {throw new Error();}}
完整代码:https://codesandbox.io/s/beautiful-neumann-ko4n5
useContext
Context是局部的全局变量
import React from "react";function Papa() {return (<div>{" "}我是Papa<Son /></div>);}function Son() {// 3. 通过useContext获得数据const { n, setN } = React.useContext(Context);return (<div>我是Son<div>{n}</div><buttononClick={() => {setN(n + 1);}}>+1</button></div>);}// 1. 创建一个Contextconst Context = React.createContext(null);export default function App() {const [n, setN] = React.useState(0);return (// 2. 将包含读写API的数据传给Provider内的组件<Context.Provider value={{ n, setN }}><Papa /></Context.Provider>);}
完整代码:https://codesandbox.io/s/youthful-wildflower-wmssd
useEffect
用途:
改变外部环境,如修改document.title
模拟生命周期
参考:https://www.yuque.com/qingrenyoutiandi/grlnzp/gxnz4y
useLayoutEffect
useEffect在浏览器渲染完成之后执行
useLayoutEffect在浏览器渲染完成之前执行
useLayoutEffect总是比useEffect先执行
优先使用useEffect,有改变layout(修改DOM)才放useLayoutEffect
import React, { useState, useLayoutEffect } from "react";import ReactDOM from "react-dom";const BlinkyRender = () => {const [value, setValue] = useState(0);useLayoutEffect(() => {document.querySelector('#x').innerText = `value: 1000`}, [value]);return (<div id="x" onClick={() => setValue(0)}>value: {value}</div>);};ReactDOM.render(<BlinkyRender />,document.querySelector("#root"));
useMemo和useCallback
useMemo和useCallback主要用于性能优化
React会有多余的render, 在下面的例子中,如果修改N, 子组件Child中的m并没有改变,Child函数仍然会执行
import { useState } from "react";function Child(props){console.log("Child执行了")return (<><div>{props.m}</div></>)}export default function App() {const [n, setN] = useState(0)const [m, setM] = useState(0)const addN = ()=>{setN(n+1)}return (<div><div>{n}</div><button onClick={addN}>changeN</button><div className="App"><Child m={m}/></div></div>);}
使用React.memo()可以使组件只有在props变化的时候才重新执行
const Child2 = React.memo(Child)
可以把Child函数声明直接写到React.memo里
const Child = React.memo((props) => {console.log("Child执行了");return (<><div>{props.m}</div></>);});
但是有一个bug,添加监听函数后一秒破功。因为只要App重新渲染,addM就会重新执行,子组件Child的props就变了,所以会重新渲染
import React, { useState } from "react";const Child = React.memo((props) => {console.log("Child执行了");return (<><div>{props.m}</div>{// 添加监听函数}<button onClick={props.addM}>changM</button></>);});export default function App() {const [n, setN] = useState(0);const [m, setM] = useState(0);const addN = () => {setN(n + 1);};const addM = () => {setM(m + 1);};return (<div><div>{n}</div><button onClick={addN}>changeN</button><div className="App"><Child m={m} addM={addM} /></div></div>);}
使用useMemo
const addM = useMemo(() => {const fn = () => setM(m + 1);return fn;}, [m]);
第一个参数()=>返回一个函数或值,第二个参数为依赖
只有当依赖变化时,才计算出新的value, 如果依赖不变,则重用之前的value
useMemo语法太繁琐了,每次都要写()=>fn/value,
为了不用写useMemo(() => () => { doSomething() })
于是就有了语法糖useCallback, 第一个参数只需要写回调函数() => { doSomething() }
const addM = useCallback(() => console.log("hello"), [m]);
完整代码:https://codesandbox.io/s/zealous-heisenberg-pzcn6
useRef
如果你需要一个值,在组件不断render时保持不变, 则使用useRef
初始化: const count = useRef(0)
读取: count.current
import { useEffect, useRef, useState } from "react";export default function App() {const [n, setN] = useState(0);const count = useRef(0);useEffect(() => {count.current += 1;console.log(count.current);});return (<div className="App"><div>{n}</div><button onClick={() => setN(n + 9)}>+9</button></div>);}
由于React的理念,当ref变化时,不会自动render
监听ref, 当ref.current变化时,调用setX即可
import { useEffect, useRef, useState } from "react";import "./styles.css";export default function App() {const [_, set_] = useState(null);const count = useRef(0);return (<div className="App"><div>{count.current}</div><buttononClick={() => {count.current += 1;set_(Math.random());console.log(count.current);}}>count+1</button></div>);}
forwardRef
props不包含ref, 不能传递ref属性,需要forwardRef实现ref的传递
import React, { useEffect, useRef } from "react";import "./styles.css";const Child = React.forwardRef((props, ref) => {console.log(ref.current);return <div ref={ref}>{props.msg}</div>;});export default function App() {const ref = useRef(null);useEffect(() => {const div = ref.current; //DidMount后, ref.current指向当前DOM元素console.log(div);});return (<div className="App"><Child msg={"hello"} ref={ref} /></div>);}
useImperativeHandle
useImperativeHandle用于对 ref 的封装, 使其不引用DOM, 而是返回子组件内的数据或函数
import React, {useRef,useState,useEffect,useImperativeHandle,createRef} from "react";import ReactDOM from "react-dom";import "./styles.css";function App() {const buttonRef = useRef(null);useEffect(() => {console.log(buttonRef.current);});return (<div className="App"><Button2 ref={buttonRef}>按钮</Button2><buttonclassName="close"onClick={() => {console.log(buttonRef);buttonRef.current.x();}}>x</button></div>);}const Button2 = React.forwardRef((props, ref) => {const realButton = useRef(null);const setRef = useImperativeHandle;setRef(ref, () => {return {x: () => {realButton.current.remove();},realButton: realButton};});return <button ref={realButton} {...props} />;});const rootElement = document.getElementById("root");ReactDOM.render(<App />, rootElement);
自定义hook
import { useState, useEffect } from "react";const useList = () => {const [list, setList] = useState(null);useEffect(() => {ajax("/list").then(list => {setList(list);});}, []); // [] 确保只在第一次运行return {list: list,setList: setList};};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);});}
stale closure
过时的闭包
参考链接:https://dmitripavlutin.com/react-hooks-stale-closures/
