UseState
使用状态
const [n, setN] = React.useState(0);
const [user, setUser] = React.useState({ name: "Z" });
注意事项1:不可局部更新
如果State是一个对象,则无法完成局部更新 例如
function App() {
const [user, setUser] = useState({ name: "Sam", age: 18 });
const onClick = () => {
setUser({
name: "Jack",
});
};
return (
<div className="App">
<h1>{user.name}</h1>
<h2>{user.age}</h2>
<button onClick={onClick}>Click</button>
</div>
);
}
当点击Click按钮时,页面上则只会显示Jack,18则会消失, 因为setState并不会帮助我们合并属性
注意事项2:地址要变
setState(obj)如果obj的地址不变,则React就认为数据没有发生变化,这和Vue3是不同的。
function App() {
const [user, setUser] = useState({ name: "Sam", age: 18 });
const onClick = () => {
user.name = "Jack";
setUser(user);
};
return (
<div className="App">
<h1>{user.name}</h1>
<h2>{user.age}</h2>
<button onClick={onClick}>Click</button>
</div>
);
}
点击按钮时,则不会发生变化,因为对象的地址没有发生变化,解决这一问题的方法写成注意事项1里的代码就好了。
useState接受函数
const [state, setState] = React.useState(() => {
return initialState;
});
//该函数返回初始值state,且执行一次
setState接受函数
setN((i) => i + 1);
useReducer
用来践行Flux/Redux的思想
分四步走
- 创建初始值initialState
- 创建所有操作
reducer(state,action) - 传给useReducer,得到读和写API
- 调用写
({type:"操作类型"})
const initial = {
n: 0,
};
const reducer = (state, action) => {
if (action.type === "add") {
return { n: state.n + action.number };
} else if (action.type === "multi") {
return { n: state.n * 2 };
} else {
throw new Error("unknown type");
}
};
function App() {
const [state, dispatch] = useReducer(reducer, initial);
const { n } = state;
const onClick = () => {
dispatch({ type: "add", number: 1 });
};
const onClick2 = () => {
dispatch({ type: "add", number: 2 });
};
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>+1</button>
<button onClick={onClick2}>+2</button>
</div>
);
}
如何代替Redux
步骤
- 将数据集中在一个store对象
- 将所有操作集中在reducer
- 创建一个Context
- 创建对数据的读写API
- 将上一步的API放入Context
- 用Context.Provider将Context提供给所有组件
- 各个组件用useContext获取读写API
useContext
上下文
全局变量是全局的上下文
上下文是局部的全局变量
使用方法
- 使用
C=createContext(initial)创建上下文 - 使用
<C.Provider>圈定作用域 - 在作用域内使用
useContext(C)来使用上下文
useContext注意事项
不是响应式的
在一个模块将C里面的值改变
另一个模块不会感知这个变化
useEffect
副作用
对环境的改变即为副作用,如修改document.title
但是我们不一定非要把副作用放在useEffect里
用途
作为componentDidMount使用,[]作第二个参数
作为componentDidUpdate使用,可指定依赖
作为componentWillUnmount使用,通过return
以上三种用途可同时存在
特点
如果同时存在多个useEffect,会按照出现次序执行
useLayoutEffect
布局副作用
useEffect在浏览器渲染完成后执行
useLayoutEffect在浏览器渲染之前执行
通过时间点侧面证明
useEffect与useLayoutEffect - CodeSandbox
特点
useLayoutEffect总是比useEffect先执行
useLayoutEffect里的任务最好影响了Layout
为了用户体验,优先使用useEffect
useMemo
首先要理解React.memo
React默认有多余的多余的render
代码中的Child用React.memo(Child)代替
如果props不变,就没有必要再执行一次函数组件
但是会出现一个bug
在添加了监听函数之后,props不变时,还是会render函数租价
因为新旧函数虽然功能一样,但是地址不一样
使用useMemo
特点
第一个参数是()=>value
第二个参数是依赖,[m,n]
只有当依赖变化时,才会计算出新的value
如果以来不变,那么就重用之前的value
注意
如果需要的value是个函数,那么就要写成
useMemo(()=>(x)=>console.log(x))
这是一个返回函数的函数
useCallback
用法
useCallback(x=>log(x),[m])等价于
useMemo(()=>x=>log(x))
useRef
目的
如果你需要一个值,在组建不断render时保持不变
初始化:const count=useRef(0)
读取:count.current
为什么需要current?
为了保证两次useRef是同一个值(只有引用能做到)
forwardRef
useRef
可以用来引用DOM对象
也可以用来引用普通对象
forwardRef
由于props不包含ref,所以需要forwardRef
useImperativeHandle
暴露一个自定义ref
useImperativeHandle - CodeSandbox