React Hooks
useState
使用状态
-
const[n,setN] = React.useState(0)--初始值数字 -
const[user,setUser] = React.useState({name:'F'})--对象
注意事项1: 不可局部更新
- 如果
state是一个对象,不能部分setState,因为setState不会帮我们合并属性
点击click之后,并不会将age:18展示出来,如果想要age:18展示出来,加上...user(把所有user属性拷贝出来,用name属性覆盖之前的name属性)
注意事项2:地址要变
- setState(obj)如果地址不变,那么React就认为数据没有变化
const onClick = () =>{
user.name = "Jack"
setUser(user)
}//是不会在页面上有任何变化的
//不能是之前的对象,必须是新的对象必须改变它的的地址
const onClick = () =>{
setUser({
...user,
name: 'Jack'
})
}
useState接受函数
const [state, setState] = useState(()=>{
return initialState
})
-
该函数返回初始
state,且只执行一次 -
useState接受函数的好处是,减少计算的过程
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 -
五、将第四步的内容放到第三步的
Context -
六、用
Context.Provider将Context提供给所有组件 -
七、各个组件用
useContext获取读写API
useContext
上下文
-
全局变量是全局的上下文
-
上下文是局部的全局变量
使用方法
-
一、使用
C=createContext(initial)创建上下文 -
二、使用
<C.Provider>圈定作用域 -
三、在作用域内使用
useContext(C)来使用上下文
const C = createContext(null)
function App() {
console.log("APP 执行了")
const [n, setN] = useState(0)
return (
<C.Provider value={{ n, setN}}>
<div className="App">
<Baba />
</div>
</C.Provider>
)
}
function Baba() {
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>
)
}
useContext注意事项
不是响应式
-
你在一个模块将C里面的值改变
-
另一个模块不会感知到这个变化
useEffect
function App(){
const [n, setN] = useState(0)
const onClick = () =>{
setN(i => i+1)
}
useEffect(()=>{
console.log("第一次渲染之后执行这一句话")
},[]) //里面的变量变化时执行 => 不会执行
useEffect(()=>{
if(n !== 0){
console.log("n变化了")
}
},[n]) //n变化时执行
useEffect(()=>{
console.log("任何一个state变化时都执行")
})
return (
<div>
n:{n}
<button onClick={onClick}>+1</button>
</div>
)
副作用(API名字叫得不好)
-
对环境的改变即为副作用,如修改
document.title -
但我们不一定非要把副作用放在
useEffect里 -
实际上叫做
afterRender更好,每次render后运行
用途
-
作为
componentDidMount使用,[]作第二个参数 -
作为
componentDidUpdate使用,可指定依赖 -
作为
componentWillUnmount使用,通过return -
以上三种用途可同时存在
特点
- 如果同时存在多个
useEffect,会按照出现次序执行
useLayoutEffect
布局副作用
-
useEffect在浏览器渲染完成后执行 -
useLayoutEffect在浏览器渲染前执行
const App = () => {
const [n, setN] = useState(0);
useEffect(() => {
document.querySelector('#x').innerText = `n: 1000`
}, [n]);
return (
<div id="x" onClick={() => setN(0)}>n: {n}</div>
);
};
特点
-
useLayoutEffect总是比useEffect先执行 -
useLayoutEffect里的任务最好影响了Layout
经验
- 为了用户体验,优先使用
useEffect(优先渲染)
useMemo
特点
-
第一个参数是
()=>value -
第二个参数是依赖
[m,n] -
只有当依赖变化时,才会计算出新的
value -
如果依赖不变,那么就重用之前的
value(类似于Vue2的computed)
注意:
-
如果你的
value是个函数,那么你就要写成useMemo(()=> (x) => console.log(x)) -
这是一个返回函数的函数(难用,所以就有了
useCallback)
useCallback
- 用法:
useCallback(x => log(x),[m])等价于useMemo(()=> x =>log(x), [m])
useRef
目的
-
如果你需要一个值,在组件不断
render时保持不变 -
初始化:
const count = useRef(0) -
读取:
count.current -
为什么需要
current?为了保证两次useRef是同一个(只有引用能做到)
- 注意:
count还是同一个count,但是count对应的current不是同一个
延伸(Vue3的ref)
-
初始化:
const count=ref(0) -
读取:
count.value -
不同点:当
count.value变化时,Vue3会自动render
useRef能做到变化时自动render吗?
-
不能,因为不符合React的理念
-
React的理念是
UI=f(date) -
但是可以自己添加
ref,当ref.current变化时,调用setX即可
function App() {
console.log("App 执行")
const [n, setN] = React.useState(0)
const [_, set_] = React.useState(0)
const onClick = () =>{
setN(n +9 )
}
const onClick2 = () =>{
count.current += 1
//set_(Math.rsndom())
console.log(count.current)
}
useEffect(()=>{
console.log(count.current)
})
return (
<div className="App">
<div>
<button onClick ={onClick}>update n {n}</button>
<button onClick2 ={onClick2}>update count: {count.current}</button>
</div>
</div>
)
}
forwardRef
讲了useRef了解一下forwardRef
- props无法传递ref属性
function App() {
const buttonRef = useRef(null)
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
{/*浏览器控制台的报错*/}
</div>
)
}
const Button2 = props =>{
return <button className="red" {...props}/>
}
- 实现ref的传递
function App() {
const buttonRef = useRef(null)
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
)
}
const Button3 = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props}/>
})
useRef
-
可以用来引用DOM对象
-
也可以用来引用普通对象
forwardRef
-
由于props不包括ref,所以需要forwardRef
-
为什么props不包含ref呢?因为大部分时候不需要
uselmperativeHandle
- 应该叫做setRef
function App() {
const buttonRef = useRef(null);
useEffect(() => {
console.log(buttonRef.current);
});
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
<button
className="close"
onClick={() => {
console.log(buttonRef);
buttonRef.current.x();
}}
>
x
</button>
</div>
);
}
const Button2 = React.forwardRef((props, ref) => {
const realButton = createRef(null);
const setRef = useImperativeHandle;
setRef(ref, () => {
return {
x: () => {
realButton.current.remove();
},
realButton: realButton
};
});
return <button ref={realButton} {...props} />;
});
- 用于自定义ref的属性
自定义Hook
封装数据的操作
分析
-
还可以在自定义Hook里使用Context
-
useState只说了不能在if里,没说不能在函数里运行,只要这个函数在函数组件里运行即可
Stale Closure
- 过时的闭包