1. React Hooks 在使用上有哪些限制?
-
只能在函数组件内部使用:React Hooks 只能在函数组件内部使用,不能在类组件中使用。这是因为 React Hooks 是基于函数式编程的思想,通过函数来管理组件的状态和生命周期。
-
只能在顶层调用:React Hooks 必须在函数组件的顶层调用,不能在条件语句、循环语句或嵌套函数中调用。这是为了保证 Hooks 的执行顺序和稳定性,以及避免对状态的错误管理。
-
Hook 名称必须以 “use” 开头:自定义的 Hook 名称必须以 “use” 开头,这是为了标识该函数是一个 Hook,并能够在其它 Hook 内使用。
-
必须按顺序调用:React Hooks 必须按照相同的顺序调用,且每次函数组件渲染时的调用顺序必须相同。这是为了确保在组件重新渲染时,React 能够正确地将之前的状态和效果与当前的状态和效果进行匹配。
-
不能在循环中使用 Hook:React Hooks 不能在循环中使用,因为循环中的闭包函数会共享相同的状态。如果需要在循环中使用 Hook,可以使用自定义 Hook 来解决这个问题。
需要注意的是,这些限制都是为了保证 React Hooks 的正确性和稳定性,遵守这些限制可以更好地使用和理解 React Hooks。
2. useEffect 与 useLayoutEffect 有什么区别?
useEffect 和 useLayoutEffect 是 React Hooks 中用于处理副作用的两个钩子函数,它们之间的主要区别在于触发的时机不同。
useEffect 会在组件渲染完成后异步执行副作用代码。它不会阻塞组件的渲染,会在浏览器绘制完成后才执行。这意味着 useEffect 中的副作用代码可能会在页面重新渲染之后才执行,因此它适合处理不需要立即执行的副作用,如数据请求、事件绑定等。
useLayoutEffect 会在组件渲染完成后同步执行副作用代码。它会在浏览器绘制之前执行,即在页面重新渲染之前执行。这意味着 useLayoutEffect 中的副作用代码会在页面重新渲染之前执行,可能会阻塞组件的渲染,因此需要注意使用 useLayoutEffect 的性能影响。它适合处理需要在页面重新渲染之前立即执行的副作用,如获取 DOM 元素的尺寸、触发动画等。
总结来说,useEffect 是一种异步执行副作用代码的钩子函数,适合处理不需要立即执行的副作用;useLayoutEffect 是一种同步执行副作用代码的钩子函数,适合处理需要在页面重新渲染之前立即执行的副作用。根据具体的需求,选择合适的钩子函数来处理副作用,可以优化和控制组件的行为。
3. React 中怎么实现状态自动保存(KeepAlive)?
什么是状态保存?
假设有下述场景:
移动端中,用户访问了一个列表页,上拉浏览列表页的过程中,随着滚动高度逐渐增加,数据也将采用触底分页加载的形式逐步增加,列表页浏览到某个位置,用户看到了感兴趣的项目,点击查看其详情,进入详情页,从详情页退回列表页时,需要停留在离开列表页时的浏览位置上
类似的数据或场景还有已填写但未提交的表单、管理系统中可切换和可关闭的功能标签等,这类数据随着用户交互逐渐变化或增长,这里理解为状态,在交互过程中,因为某些原因需要临时离开交互场景,则需要对状态进行保存
在 React 中,我们通常会使用路由去管理不同的页面,而在切换页面时,路由将会卸载掉未匹配的页面组件,所以上述列表页例子中,当用户从详情页退回列表页时,会回到列表页顶部,因为列表页组件被路由卸载后重建了,状态被丢失。
在 React 中,可以通过使用组件的状态和生命周期方法来实现状态自动保存(KeepAlive)的功能。下面是一种实现方式:
- 创建一个高阶组件(Higher-Order Component,HOC)来包装需要进行状态自动保存的组件。这个高阶组件的作用是在组件销毁时将组件的状态保存下来,并在组件重新Mount时将之前保存的状态恢复。
import React, { Component } from 'react';
const withKeepAlive = WrappedComponent => {
return class extends Component {
constructor(props) {
super(props);
this.state = {
savedState: null
};
}
componentDidMount() {
if (this.state.savedState) {
this.setState({ savedState: null });
}
}
componentWillUnmount() {
this.setState({ savedState: this.props });
}
render() {
const { savedState } = this.state;
if (savedState) {
return <WrappedComponent {...savedState} />;
}
return <WrappedComponent {...this.props} />;
}
};
};
- 在需要进行状态自动保存的组件上使用这个高阶组件进行包装。
import React from 'react';
const MyComponent = ({ count, setCount }) => {
// ...组件的实现
};
export default withKeepAlive(MyComponent);
这样,当这个被包装的组件被销毁时,其状态会被保存下来。当组件重新 Mount 时,之前保存的状态会被恢复,实现了状态的自动保存。
需要注意的是,这只是一种简单的实现方式,如果需要更复杂的 KeepAlive 功能,可以考虑使用 React 的 Context 或 Redux 等状态管理工具来实现。
4. mobx 和 redux 有什么区别?
MobX 和 Redux 都是用于状态管理的库,它们之间有以下区别:
-
哲学不同:MobX 倾向于提供简单和直观的 API,使得状态管理变得更加简单和自然。它使用可观察对象和自动追踪的方式来管理状态,通过对状态的直接修改来触发自动更新。而 Redux 则更倾向于函数式编程的思想,通过纯函数的方式对状态进行更新,通过触发 action 来派发更新。
-
数据流管理方式不同:在 MobX 中,状态的变化是自动的,当状态被观察的时候,任何对状态的修改都会被自动追踪并触发相关的更新。这种方式下,状态的更新是一种直接的、隐式的方式。而 Redux 中,状态的更新是显式的,通过派发 action 来改变状态,再通过 reducer 函数来返回新的状态。
-
API 复杂度不同:由于 MobX 的设计理念是提供简单和直观的 API,因此使用 MobX 可以更快地上手并进行状态管理。而 Redux 的设计更加严格,需要编写额外的 action 和 reducer 函数,因此在一些复杂的应用中,会需要更多的代码和配置。
-
性能差异:由于 MobX 使用了自动追踪的方式来管理状态,它可以做到更细粒度的更新,只更新相关的组件,因此在某些场景下,MobX 的性能可能会比 Redux 更好。但是,在一些需要更严格控制状态更新的场景下,Redux 可能更适合。
综上所述,MobX 更加面向简单和直观的状态管理,适用于快速开发和小型应用;而 Redux 更加严格和可控,适用于大型应用和需要严格控制状态更新的场景。根据具体的需求,选择合适的状态管理库可以提高开发效率和应用性能。
5. 下面函数组件的输出分别是什么?
下面是一个简单的函数组件,有两个按钮:“alert”、“add”。
如果先点击“alert”按钮,再点击一次“add”按钮,那么弹窗框中的值和页面中展示value分别是什么?
const FunctionComponent = () => {
const [value, setValue] = useState(1)
const log = () => {
setTimeout(() => {
alert(value)
}, 3000);
}
return (
<div>
<p>FunctionComponent</p>
<div>value: {value}</div>
<button onClick={log}>alert</button>
<button onClick={() => setValue(value + 1)}>add</button>
</div>
)
}
答:
展示的value是2,alert的值是1
我们发现弹出的值和当前页面显示的值不相同。
换句话说:log 方法内的 value 和点击动作触发那一刻的 value 相同,value 的后续变化不会对 log 方法内的 value 造成影响。
这种现象被称为“闭包陷阱”或者被叫做“Capture Value” :函数式组件每次render 都会生产一个新的 log 函数,这个新的 log 函数会产生一个在当前这个阶段 value 值的闭包。
上面例子 “闭包陷阱” 的分析:
- 初始次渲染,生成一个 log 函数(value = 1)
- value 为 1 时,点击 alert 按钮执行 log 函数(value = 1)
- 点击按钮增加 value,比如 value 增加到 6,组件 render ,生成一个新的 log 函数(value = 6)
- 计时器触发,log 函数(value = 1)弹出闭包内的 value 为 1
如何让弹窗中展示最新的value值呢?
-
使用函数式更新:在使用状态更新函数时,可以传递一个回调函数,而不是直接使用状态的值。这样可以确保回调函数中引用的是最新的状态值。
-
使用 useEffect 进行清理:如果在 useEffect 中使用了闭包引用了状态或属性,需要在 useEffect 的返回函数中进行清理。这样可以确保在组件卸载时,闭包中对状态或属性的引用被释放。
-
使用 useRef 或 useCallback:可以使用 useRef 或 useCallback 来缓存函数或值,以避免闭包陷阱。这样可以确保引用的是固定的函数或值,不受组件重新渲染的影响。
举个例子:
使用 useRef 解决闭包陷阱的问题
const FunctionComponent = () => {
const [value, setValue] = useState(1)
const countRef = useRef(value)
const log = () => {
setTimeout(() => {
alert(countRef.current)
}, 3000);
}
useEffect(() => {
countRef.current = value
}, [value])
return (
<div>
<p>FunctionComponent</p>
<div>value: {value}</div>
<button onClick={log}>alert</button>
<button onClick={() => setValue(value + 1)}>add</button>
</div>
)
}