react 复用 render Props 、 High order component 和 Hook
[cc lang=”js”]// render props
class Mouse Tracker extends React.Component {
render ( ) {
return (
< div >
< h1 > 移动鼠标! </ h1 >
< Mouse render = { mouse => (
// Mouse 组件内 props.render(mouse) , Mouse 组件为props.render函数提供 mouse 坐标参数,渲染内容为render组件内容
) } />
// render 函数因为是一个函数直接量, 更新时每次都是不同的, 可以定义一个函数变量
) ;
}
} [/cc]
React Hook
[cc lang=”js”]
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
// 如果 props.friend 发生变化,这将导致订阅不能取消, 从而导致内存泄露或崩溃的问题
// useEffect 增加didUpdate,解决了 class 组件中因为没有处理更新逻辑而导致常见的 bug
useEffect(() => {
// …
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
react hook 解决更新中重新订阅,并删除上一次订阅
[/cc]
!! 通过跳过 Effect 进行性能优化
prevProps 或 prevState
[cc lang=”js”]
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}[/cc]
Effect 加入第二个参数
[cc]
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新[/cc]
!!Effect 第二个参数
// 依赖列表中省略函数
!!如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组 []
1 如果函数里没有用到 props 或state, 第二个参数可以是 [], 这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行
2. 如果函数里用到了, 第二个参数 需要加入 [someProp]
3. setState
的函数式更新形式 解决性能问题,
*******关注点分离 创建多个 effect 干不同的事
[cc lang=”js”]
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// …
}[/cc]
**************只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook
*********自定义hook
[cc lang=”js”]
import React, { useState, useEffect } from ‘react’;
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
// 使用 hook
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
{props.friend.name}
);
}[/cc]
******要点
1 函数式的 setState 结合展开运算符来达到合并更新对象的效果
[cc lang=”js”]setState(prevState => {
// 也可以使用 Object.assign
return {…prevState, …updatedValues};
});[/cc]
2 effect
* effect 的依赖发生变化,它就会被重新创建
* 请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得处理额外操作很方便
* 要记住 effect 外部的函数使用了哪些 props 和 state 很难。这也是为什么 通常你会想要在 effect 内部 去声明它所需要的函数。 这样就能容易的看出那个 effect 依赖了组件作用域中的哪些值
* 可以通过effect 内部声明变量, 并返回修改变量的函数, 例如组件已经卸载 但请求还在继续,清除卸载hook时设置变量,让状态不更新
[cc lang=”js”]useEffect(() => {
let ignore = false;
async function fetchProduct() {
const response = await fetch(‘http://myapi/product/’ + productId);
const json = await response.json();
if (!ignore) setProduct(json);
}
fetchProduct();
return () => { ignore = true };
}, [productId]);[/cc]
***** 你可以把函数加入 effect 的依赖, 但把它的定义包裹进useCallback Hook。这就确保了它不随渲染而改变,除非 它自身 的依赖发生了改变
[cc lang=”js”] function ProductPage({ productId }) {
// ✅ 用 useCallback 包裹以避免随渲染发生改变
const fetchProduct = useCallback(() => {
// … Does something with productId …
}, [productId]); // ✅ useCallback 的所有依赖都被指定了
return ;
}
function ProductDetails({ fetchProduct }) {
useEffect(() => {
fetchProduct();
}, [fetchProduct]); // ✅ useEffect 的所有依赖都被指定了
// …
}[/cc]
***** 如果我的 effect 的依赖频繁变化,我该怎么办
[cc lang=”js”]function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 这个 effect 依赖于 `count` state
}, 1000);
return () => clearInterval(id);
}, []); // ? Bug: `count` 没有被指定为依赖 // 如果指定count 依赖,effect 改变count,effect就会重新执行,死循环
return
{count}
;
}
// setState 的函数式更新形式 ,它允许我们指定 state 该 如何 改变而不用引用 当前 state
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
}, 1000);
return () => clearInterval(id);
}, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量
return
{count}
;
}
[/cc]
3 useReduce
你可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)
[cc lang=”js”]
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case ‘increment’:
return {count: state.count + 1};
case ‘decrement’:
return {count: state.count – 1};
case ‘reset’:
return init(action.payload); //重置 state 的 action 做处理提供了便利
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
Reset
>
);
}[/cc]
4.useCallback
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
[cc lang=”js”]const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);[/cc]
5.useMemo
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
[cc lang=”js”]const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);[/cc]
6. useRef
[cc lang=”js”]
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
>
);
}[/cc]
请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现 ref