Hook基础学习
1. useState()
const [state, setState] = useState(initalState) // 解构赋值
2. useEffect()
useEffect( () => {}, ?[dependencies]); // 第二项非必填
function Demo3() {
const [data, setData] = useState(0);
useEffect(() => {
console.log("useEffect--[]");
setData(20000);
setTimeout(() => setData(10000), 10000);
console.log(data);
}, []);
useEffect(() => {
console.log("useEffect ---> 无依赖");
});
useEffect(() => {
console.log("useEffect 依赖data: data发生了变化");
}, [data]);
return (
<div>
{(() => {
console.log("render");
return null;
})()}
<p>data: {JSON.stringify(data)}</p>
</div>
);
}
输出结果为
- 可见setState是异步执行的
- useEffect 是在render之后按先后顺序生效执行的
- useEffect 在没有任何依赖的情况下(
useEffect( () => {})),render后每次都执行 - 依赖
[]可以实现类似componentDidMount的作用,render后只执行一次
3. useContext
跨组件数据共享
const value = useContext(MyContext);
// MyContext 为 context 对象(React.createContext 的返回值)
// useContext 返回MyContext的返回值。
// 当前的 context 值由上层组件中距离当前组件最近的<MyContext.Provider> 的 value prop 决定。
eg.
import React, { useContext, useState } from “react”;
const MyContext = React.createContext();
function Demo5() {
const [value, setValue] = useState("init");
console.log("Demo5");
return (
<div>
{(() => {
console.log("render");
return null;
})()}
<button onClick={() => {
console.log('click:更新value')
setValue(`${Date.now()}_newValue`)
}}>
改变value
</button>
<MyContext.Provider value={value}>
<Parent1 />
<Parent2 />
</MyContext.Provider>
</div>
);
}
function Parent1() {
console.log("Parent1");
return <Child1 />;
}
function Parent2() {
console.log("Parent2");
return <Child2 />;
}
function Child1() {
const value = useContext(MyContext);
console.log("Child1-value", value);
return <div>Child1-value: {value}</div>;
}
function Child2(props) {
console.log('Child2')
return <div>Child2</div>;
}
输出结果:
<MyContext.Provider>的 value 发生变化时候, 包裹的组件无论是否订阅content value,所有组件都会从新渲染,所以<MyContext.Provider>包裹的越多,层级越深,性能会造成影响。- parent2 没有订阅value不应该rerender, 如何避免不必要的render?可以使用React.memo优化。
const Parent2 = React.memo(() => {
console.log("Parent2");
return <Child2 />;
})
输出结果:
注意:
通过memo第二个参数可以控制对比过程。(具体看下memo的使用,待补充。。)
4. useRef 和 useImperativeHandle
useRef使用场景
- 用于获取并存放组件的 dom 节点, 以便直接对 dom 节点进行原生的事件操作
- 操作原生子组件
- 一共分两个步骤:
useRef创建一个ref对象ref={xx}挂到react元素上
- 一共分两个步骤:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);//创建一个包含current属性的对象
console.log(textInput);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input type="text" ref={textInput} />//挂到内部dom上
<input type="button" value="Focus the text input" onClick={handleClick} />
</div>
);
}
-
操作自定义自组件(需要
forwardRef配合使用)forwardRef是一个可选特性,其允许某些组件接收ref,并将其向下传递(换句话说,“转发”它)给子组件。- 第二个参数
ref只在使用React.forwardRef定义组件时存在。常规函数和 class 组件不接收ref参数,且 props 中也不存在ref。 上面的例子可以写成:
function CustomTextInput(props) { // 这里必须声明 textInput,这样 ref 才可以引用它 const textInput = useRef(null); console.log(textInput); function handleClick() { textInput.current.focus(); } return ( <div> <Child ref={textInput} /> //**依然使用ref传递** <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); } const Child = forwardRef((props, ref) => { //** 看我 ** return <input type="text" ref={ref} />;//** 看我挂到对应的dom上 ** });forwardRef的做法本身没有什么问题, 但是我们是将子组件的DOM直接暴露给了父组件:
- 直接暴露给父组件带来的问题是某些情况的不可控
- 父组件可以拿到DOM后进行任意的操作
- 我们只是希望父组件可以操作的focus,其他并不希望它随意操作其他方法
- useImperativeHandle介绍
useImperativeHandle(ref, createHandle, [deps])
通过useImperativeHandle可以只暴露特定的操作 1.通过useImperativeHandle的Hook, 将父组件传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起 2.所以在父组件中, 调用inputRef.current时, 实际上是调用返回的对象上面的例子可以写成:
const Child = forwardRef((props, ref) => { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input type="text" ref={inputRef}/>; });
- 利用 useRef 解决由于 hooks 函数式组件产生闭包时无法获取最新 state 的问题。
看下面例子,点击add 在state 为6时,点击输出,继续点击add ,输出时 state时多少呢?是界面上 tate 的实时状态 ? 还是在点击 button 时tate 的快照 ?
function Demo() {
const [stateNumber, setStateNumber] = useState(0);
function alert() {
setTimeout(() => console.log(stateNumber), 2000);
}
function add() {
setStateNumber(stateNumber + 1);
}
return (
<div>
<button onClick={alert}>输出</button>
<h4>state: {stateNumber}</h4>
<button onClick={add}>add</button>
</div>
);
}
最终结果是,当点击add增加state到6时,再继续增加state,点击输出,输出的值为6而不是12.
原因是,当点击add 更新state状态时,React 会重新渲染组件,会重新执行一遍add函数,里面的局部变量包括
state会再一次创建并且被赋值,当state = 6时,点击输出,此时,执行alert函数,state 为6,再次点击点击add 更新state状态时,局部变量包括state会再一次创建并且被赋值,而alert函数中state值依旧指向上一次的值,因此输出还是6。
function Demo() {
const [stateNumber, setStateNumber] = useState(0);
const res = useRef(0);
useEffect(() => {
res.current = stateNumber;
});
function alert() {
setTimeout(() => console.log(res.current), 2000);
}
function add() {
setStateNumber(stateNumber + 1);
}
return (
<div>
<button onClick={alert}>输出</button>
<h4>state: {stateNumber}</h4>
<button onClick={add}>add</button>
</div>
);
}
此时输出的结果为最新值,因为使用useRef,返回的始终是一个相同的引用,也就是说赋值的时候始终是对res的current属性进行赋值,因此add函数重新执行的时候,ref返回的值始终是对current的引用,没有变,因此可以返回最新值。
- 获取上一个值
利用useEffect每次render后才执行和useRef引用在整个生命周期中保持不变的特性,封装成自定义 获取上一个值的hook。
const usePrevProps = (value) => {
const preRef = useRef(0);
useEffect(() => {
preRef.current = value;
});
return preRef.current;
};
tips
useRef相当于this,只是一个引用,在整个生命周期内保持不变,因此,当useRef的current属性改变时,并不会造成re-render,因此ref.current 不可以作为其他hooks(useMemo, useCallback, useEffect)的依赖项,也不会造成页面的主动渲染
function Demo() {
const [minus, setMinus] = useState(0);
const ref = useRef(null);
const r = useRef(0);
const handleClick = () => {
setMinus(minus + 1);
};
console.log(`ref.current=${ref.current && ref.current.innerText}`)
useEffect(() => {
r.current += 1;
if (r.current > 1) {
console.log("r.current:" + r.current);
}
});
// #1 uesEffect
useEffect(() => {
console.log(`denp[ref.current] >`, ref.current && ref.current.innerText);
}, [ref.current]);
// #2 uesEffect
useEffect(() => {
console.log(`denp[minus]>`, ref.current && ref.current.innerText);
}, [minus]);
return (
<div className="App">
{console.log("ref的current:", ref.current)}
<h1 ref={ref}>Num: {minus}</h1>
<h1>r的current:{r.current}</h1>
<button onClick={handleClick}>Add</button>
</div>
);
}
输出分析:
- 第一次输出:
分析: 依赖项解析是在
render阶段发生的,发生在ref.current更新之前,而useEffect是在render之后执行。此时r.current值为1,不符合输出条件。 - 点击add之后,输出:
分析:console输出时,<h1>还没加载,此时ref.current值为<h1>Num: 0<h1>,所以
console.log输出ref.current=Num: 0,render时,effetc未运行,r.current值为1,因此屏幕显示r的current:1,render之后,effetc依次输出, #1 uesEffect的依赖项发生变化,输出当前dom,r.current+1,此时值为2。
- 再次点击add后,输出:
分析: 此时ref.current值为<h1>Num: 1<h1>,所以 #1 uesEffect的依赖项没有发生变化,故 #1 uesEffect的effect函数不会被执行。