1. React Hooks优点
2. useState
2.1 组件函数每次渲染都会被调用,在每一次调用中count值都是常量
1. 组件第一次渲染的时候,函数组件是从上到下执行,从useState()拿到count的初始值0, 并返回JSX
2. 点击按钮 触发onClick事件, 调用setCount(),React会再次调用组件, 函数组件会再次执行, 调用useState() 获取更新后的值, 并返回jsx。最后,React更新DOM
3. 在线demo
function App() {
const [count, setCount] = useState(0);
console.log("render...", count);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
4. 执行过程描述: 组件第一次渲染的时候,从useState()拿到count的初始值0。当我们调用setCount(1),React会再次渲染组件,这一次从useState()拿到count的值是1, 以此类推
// 第一次渲染, 执行函数组件...
function App() {
const count = 0; // 从useState()拿到count的值为 初始值 0
// ...
<p>You clicked {count} times</p>
// ...
}
// 点击按钮, 调用setCount(0+1), React会再次渲染组件, 执行函数组件...
function App() {
const count = 1; // 从useState()拿到count的值为 1
// ...
<p>You clicked {count} times</p>
// ...
}
// 点击按钮, 调用setCount(1+1), React会再次渲染组件, 执行函数组件...
function App() {
const count = 2; // 从useState()拿到count的值为 2
// ...
<p>You clicked {count} times</p>
// ...
}
2.2 每一次渲染都有它自己的事件处理函数
function App() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
console.log("You clicked on: " + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}
1. 我们的组件函数每次渲染都会被调用,但是每一次调用中count值都是常量,并且它被赋予了当前渲染中的状态值
2. 在每一次的渲染中, 事件处理函数 'handleAlertClick' 都是一个新的函数, 这些函数“看到”的是属于它那次特定渲染中的count状态值
3. 在线demo
4. 执行过程描述: 在每一次的渲染中, 组件函数都会被调用, 在每一次的调用中 都定义了一个事件处理函数 handleAlertClick, 因为闭包的关系, 延迟打印出来的值 也不会改变.
// 第一次渲染, 执行函数...
function App() {
const count = 0; // Returned by useState()
// ...
function handleAlertClick() {
setTimeout(() => {
console.log("You clicked on: " + count);
}, 3000);
}
// ...
}
// 点击按钮, 调用setCount(0+1), React会再次渲染组件, 执行函数...
function App() {
const count = 1; // Returned by useState()
// ...
function handleAlertClick() {
setTimeout(() => {
console.log("You clicked on: " + count);
}, 3000);
}
// ...
}
// 点击按钮, 调用setCount(1+1), React会再次渲染组件, 执行函数...
function App() {
const count = 2; // Returned by useState()
// ...
function handleAlertClick() {
setTimeout(() => {
console.log("You clicked on: " + count);
}, 3000);
}
// ...
}
2.3 惰性的初始值, 减少初始值的计算
1. useState可以接受一个函数, 函数的返回值作为初始值, 这个函数值在第一次渲染组件的时候调用
function App(){
const [count, setCount] = useState(()=>{
return parseInt(Math.random * 10, 10);
});
// ...
}
2.4 函数式更新, 可以接受一个函数 setCount(prevCount => nextCount)
function App() {
const [count, setCount] = useState(0);
console.log("render...", count);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count => count+1 )}>Click me</button>
</div>
);
}
3. useEffect
3.1 每次渲染都是一个新的 Effect
1. 每一次渲染, 调用 useEffect 的参数 effect 函数 都不相同
2. useEffect 分两步: 1. 在函数组件执行时, 记录新的副作用函数. 2. 在DOM更新之后, 执行副作用函数
3. 第二个参数是依赖, 没有设置依赖, 则每次渲染完成之后 执行副作用
4. 在线demo
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`useEffect >>> You clicked ${count} times`, Date.now());
});
console.log("render...", count, Date.now());
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
3.2 异步副作用
1. 连续点击3次按钮, 打印结果?
2. 在线demo
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
console.log(`You clicked ${count} times`);
}, 3000);
});
console.log("render...", count);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
3.3 副作用的清理
1. 第一次渲染
i. 执行函数式组件, 根据初始值获取state, 记录 副作用1, 最后返回jsx
ii. 渲染到浏览器
iii. 执行 副作用1
2. 点击按钮, 调用setCount() 更新数据, 触发第N次渲染, N大于等于2
i. 执行函数式组件, 获取更新后的state, 记录 副作用N, 最后返回jsx
ii. 渲染到浏览器
iii. 执行 副作用N-1 返回的 清理函数
iv. 执行 副作用N
3. 在线demo
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("++++++++++ 执行副作用", count);
let time = setTimeout(() => {
console.log(`setTimeout >>> count值:${count}`);
}, 1000);
return () => {
console.log("---------- 执行清理操作", `读取到的是上一次的值 ${count}`);
clearTimeout(time);
};
});
console.log("render...", count);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
3.4 useEffect的依赖
1. 第二个参数表示依赖, 当依赖发生变化时,副作用才会重新执行, 否则跳过执行
2. 如果第二个参数为空, 副作用每次渲染完成之后都会执行. 如果传递空数组, 表示没有依赖, 只会在第一次渲染完成后执行.
3. 第一次渲染, 根据初始值获取state, 记录 effect, dom渲染完成, 最后 执行副作用函数
4. 点击按钮, 更新数据, 触发第二次渲染, 获取最新的state, 记录 新的effect, dom渲染完成, 依赖是空数组, 没有发生变化, 跳过副作用函数的执行
5. 在页面卸载之前, 执行 副作用返回的清理函数
6. 在线demo
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times`);
return ()=>{
console.log('willUnMount');
alert("willUnMount");
};
}, []);
console.log("render...", count);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
3.5 useEffect里使用setInterval
1. 执行过程?
2. 问题在哪里?
3. 在线demo
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
console.log(`setCount(${count} + 1)`);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
3.6 useEffect 可变的依赖
1. 在线demo
type ICallback = () => void;
/**
* 使用 setInterval 执行回调
* @param callback 回调函数
* @param delay 间隔时间, 单位毫秒
*/
function useInterval(callback: () => void, delay: number | null) {
const callbackRef = useRef<ICallback>();
useEffect(() => {
// 保存最新的回调函数
callbackRef.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) {
return;
}
const timer = setInterval(() => {
// 调用最新的回调函数
callbackRef.current && callbackRef.current();
}, delay);
return () => {
clearInterval(timer);
};
}, [delay]);
}
3.7 DOM逻辑复用
/**
* 阻止滚动穿透
*/
function useStopBodyScroll(status: Boolean = true) {
/**
* 副作用, 滚动页面穿透
*/
useEffect(() => {
if (!status) {
return;
}
// 记录滚动条纵坐标
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
document.body.style.cssText += 'position:fixed;width:100%;top:-' + scrollTop + 'px;';
return () => {
let top = String(scrollTop);
document.body.style.position = '';
document.body.scrollTop = document.documentElement.scrollTop = parseInt(top, 10);
document.body.style.top = '';
};
}, [status]);
}
function App(){
useStopBodyScroll();
return <h1>{count}</h1>;
}
3.8 有状态的逻辑复用
/**
* 设置状态
*/
type ISetStatus = (status: boolean) => void;
/**
* 简单倒计时, 默认关闭
* @param count 倒计时初始值
* @param onFinish 回调
* @param reset 倒计时结束时 是否重置
*/
function useSimpleCountDown(
count: number,
onFinish?: () => void,
reset = true
): [number, ISetStatus] {
// 倒计时
const [diffTime, setDiffTime] = useState(count);
/**
* 倒计时状态, 用来关闭定时器
*/
const [active, setActive] = useState<boolean>(false);
/**
* 设置状态
*/
const setStatus = useCallback((status: boolean) => {
// 重置数值
if (status || reset) {
setDiffTime(count);
}
// 设置状态
setActive(status);
}, []);
/**
* 开启或关闭定时器
*/
useEffect(() => {
if (!active) {
return;
}
const timer = setInterval(() => {
setDiffTime((prevDiff) => {
if (prevDiff > 0) {
return prevDiff - 1;
}
setStatus(false);
onFinish && onFinish();
return 0;
});
}, 1000);
return () => {
clearInterval(timer);
};
}, [active]);
return [diffTime, setStatus];
}