一、React Hook是什么
这是官网的解释:_Hook_ 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
阮一峰大神的解释:React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。
我的理解:Hook是可以提供给我们更加简洁的方式去使用React的其他特性。
二、React Hook的性质
- 完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
- 100% 向后兼容的。 Hook 不包含任何破坏性改动。
- 现在可用。 Hook 已发布于 v16.8.0。
三、为什么要使用React Hook
- 在组件之间复用状态逻辑很难
可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
- 复杂组件变得难以理解
我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
- 难以理解的class
Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。
四、目前遇到的Hook
1.useState
const [state, setState] = useState(initialState);
返回值:state,更新state的函数setState
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。setState(newState);
React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化。
参数:初始的state:initialState
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
示例代码:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
>
);
}
注意:与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
2.useEffect
该Hook接收一个包含命令式、且有可能有副作用代码的函数。
useEffect(didUpdate); //didUpdate指的是包含命令式、且有可能有副作用代码的函数
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。
[](https://zh-hans.reactjs.org/d...,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。以下就是一个创建订阅的例子:
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
默认情况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被重新创建。
然而,在某些场景下这么做可能会矫枉过正。比如,在上面的例子中,我们不需要在每次组件更新时都创建新的订阅,而是仅需要在 source prop 改变时重新创建。
要实现这一点,可以给 useEffect 传递第二个参数,它是 effect 所依赖的值数组。更新后的示例如下:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source], //第二个参数是Effect的依赖项,在这里,只有当该数组发生变化的时候,才去执行useEffect
);
此时,只有当 props.source 改变后才会重新创建订阅。
3.useContext
const value = useContext(MyContext); //MYContext是创建好的context对象
- 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的
的 value prop 决定。 - 当组件上层最近的
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。 - 调用了 useContext 的组件总会在 context 值变化时重新渲染。
- useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者
注意:
1.useContext(MyContext) 只是让你能够_读取_ context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用
示例代码:
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
);
}
function Toolbar(props) {
return (
);
}
function ThemedButton() {
const theme = useContext(ThemeContext); //这里订阅到了provider传递下来的value值
return (
);
}
4.useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
useReducer和useState的比较:这两者都是返回了新的状态和一个函数。在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化。
以下是用 reducer 重写 useState 一节的计数器示例:
const initialState = {count: 0};
function reducer(state, action) { //reducer
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
>
);
}
第三个参数:init
你可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)。
还是上一个计数器的代码,这里使用了第三个参数添加了一个reset功能:
function init(initialCount) { //init函数:用于惰性创建初始state
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);
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init); //init函数作为第三个参数传入useReducer
return (
<>
Count: {state.count}
>
);
}
5.useRef
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
一个常见的用例便是命令式地访问子组件:
function TextInputWithFocusButton() {
const inputEl = useRef(null); //创建一个ref对象
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
//将创建好的ref对象放进ref属性中
>
);
}
useRef和ref属性的区别:
- 相同点:以我们之前对refs的了解,如果将 ref 对象以 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。
- 不同点:然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
注意:
当 ref 对象内容发生变化时,useRef 并_不会_通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
6.useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 参数:
- 一个函数,执行一些复杂的计算操作
- 一个数组,是第一个参数执行的依赖值组成的数组
- 返回值:一个memoized值
注意点:
- 把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
- 记住,传入 useMemo 的函数会在渲染期间执行(useEffect是在渲染之后才执行)。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
- 如果没有提供依赖项数组,useMemo在每次渲染时都会计算新的值。
- 你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。
- 依赖项数组不会作为参数传给“创建”函数。虽然从概念上来说它表现为:所有“创建”函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。
与useEffect的区别:
- useMemo是在组件渲染的时候执行,而useEffect是在组件渲染完之后延迟执行
- 不要在传入useMemo的函数中执行与渲染无关的操作,诸如副作用之类的操作属于useEffect的适用范围,而不是useMemo
补充:副作用
在计算机科学中,函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。
对于运行在浏览器的JS来说,副作用包括且不限于:
- console.log( )
- 操作DOM
- http请求
- 修改全局变量
- ...
五、创建自己的Hook
上例的Hooks代码还可以封装起来,变成一个自定义的Hook,便于共享。
const usePerson = (personId) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId]);
return [loading, person];
};
上面代码中,usePerson()就是一个自定义的Hook。
Person组件就改用这个新的钩子,引入封装的逻辑。
const Person = ({ personId }) => {
const [loading, person] = usePerson(personId);
if (loading === true) {
return Loading ...
;
}
return (
You're viewing: {person.name}
Height: {person.height}
Mass: {person.mass}
);
};
本文未完持续更新中...