
Ⅰ. useState
在React函数组件中,是默认只有属性没有状态的,我们可以使用如下代码用useState读写变量和设定初始值 :
const [n,setN] = react.useState(0)
const [user,setUser] = react.useState({name:'Jack'})
💡 注意事项
- state不可以局部更新
我们无法局部setState,因为react函数组件不会帮我们合并属性。解决办法是使用react提供的...语法将原属性拷贝并放在更新值之前。
- set的对象必须是新对象
因为setState(obj)中obj如果是在原来的对象上进行修改,那么对象的地址没有更改,react会将这个对象判定为原对象,则更改无效。解决方法是始终设置新的对象就可以了。
- useState接受函数,setState优先使用函数
① useState的初始值并不局限于对象,可以用函数的形式设定。
const [state, setState] = useState(()=>{ return initialState })
② 由于表达式的局限性,如果你需要在setState中进行多次操作,那么需要使用函数的形式以确保每一次的操作是有效的。
const onClick = ()=>{
setN(n+1) //无效操作
setN(n+1) //你会发现 n 不能加 2
}
const onClick = ()=>{
setN(i=>i+1) //有效操作
setN(i=>i+1) //最后值为n+2
}
Ⅱ.useReducer
如果你发现有几个变量需要汇总在一起,你可以使用复杂版的useState —— useReducer,它的好处就是可以对这个对象进行一个整体的操作 。并且可以践行React社区一直推崇的Flux/Redux思想,在我看来这个思想可能会在不久的将来Hooks思想盛行之后逐渐被取代,但是目前还是需要了解这个被广泛应用的Hook。
使用useReducer分为四个步骤:
- 创建初始值
initialState - 创建所有操作
reducer(state,action) - 传给
useReducer得到读和写API - 调用写
({type:"操作类型"})
一个🌰:
const initial = {
n:0
};
const reducer = (state,action) => {
if(action.type === "add") {
return { n:state.n + 1 };
} 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: "multi", number: 2 });
};
return (
<div className = "App">
<div>n: {n}</div>
<button onClick={onClick}>+1</button>
<button onClick={onClick2}>*2</button>
</div>
);
}
如何代替redux?
- 将数据集中在一个store对象;
- 将所有操作集中在reducer;
- 创建一个Context;
- 创建对数据读写的API;
- 将第四步的操作放进Context中;
- 用Context.Provider将Context提供给i所有组件;
- 各个组件用useContext获取读写API。
Ⅲ.useContext
这个Hook翻译过来就是“上下文”。何为上下文?上下文就是你在运行一个程序的时候所要知道的所有变量。全局变量就是全局的上下文,上下文是局部的全局变量。上一个Hook中我们已经了解到useContext的用法。
一个🌰:
const C = createContext(null);
function App() {
const [n, setN] = useState(0);
return (
<C.Provider value={{ n, setN }}>
<div className="App">
<Dog />
</div>
</C.Provider>
);
}
function Dog() {
const { n, setN } = useContext(C);
return (
<div>
狗 n: {n} <Child />
</div>
);
}
function Child() {
const { n, setN } = useContext(C);
const onClick = () => {
setN(i => i + 1);
};
return (
<div>
狗儿子 n: {n}
<button onClick={onClick}>+1</button>
</div>
);
}
Ⅳ.useEffect
这个Hook翻译过来为“副作用”,我认为把它叫做afterRender更好,因为这是每次render之后就会调用的一个函数。 用途:
-
作为componentDidMount(出生后)使用
useEffect(()=>{console.log('hi'),[]}) //使用空数组表示第一次渲染时 -
作为componentUpdate(更新)使用
useEffect(()=>{console.log('hi'),[n]}) //表示当{n}变化时useEffect(()=>{console.log('hi')}) //表示每一次更新时 -
作为componentWillUnmount(将死)使用
useEffect(()=>{console.log('hi')}) return ()=>{} //添加return表示当组件将时执行某操作
useLayoutEffect
- 布局副作用:useEffect在渲染后执行,useLayoutEffect在渲染前执行。
- 特点: useLayoutEffect总是比useEffect先执行。
- 经验: 为了用户体验,优先使用useEffect(优先渲染)。
Ⅴ.useMemo & useCallback
由于react默认会有多余的render出现,如果props不变就没要再次执行这个函数组件,useMemo就是阻止函数组件无效执行的Hook。
❗❗ 如果在函数组件中添加一个监听事件,则useMemo会直接无效,需要写成:
useMemo(()=> (x) => console.log(x))这种形式,一个返回函数的函数。
由于这种方式难用且难以理解,于是诞生了useCallback。
useCallback(x => log(x), [m]) //等价于下面一种写法
useMemo(() => x => log(x), [m])
Ⅵ.useRef
如果你需要一个值在组件不断render的时候保持始终是同一个值地址不变,那就需要用到useRef。
首先需要初始化:const n = useRef(0)
读取时则是:n.current (这里的current是用来保证两次useRef的是相同的值的引用)
❗❗ useRef是不能做到变化的时候自动render的,因为这不符合react的理念,如果你想要这个功能,完全可以自己加,监听ref,当ref.current变化的时候,调用setX即可。
Ⅶ.自定义Hook
自定义Hook必须以use开头,这是react既定的语法。通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
一个🌰:
function App() {
const { list } = useList();
return (
<div className="App">
<h1>List</h1>
{list ? (
<ol>
{list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ol>
) : (
"加载中..."
)}
</div>
);
}
与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。换句话说,它就像一个正常的函数,我们可以最大限度的灵活使用这个Hook。