1React的状态提升
React 中的“状态提升”是指将组件的状态(state)移动到其父组件中,以便多个子组件可以共享相同的状态。通过将状态提升到共同的父组件中,你可以确保这些子组件之间的状态保持同步,并且可以更方便地管理和更新状态。
以下是状态提升的一般步骤:
-
确定共享状态的位置: 确定哪些组件需要共享相同的状态。这通常是那些需要同步更新的组件。
-
将状态移动到共同的父组件中: 将这些组件中的状态移动到它们的最近共同父组件中。这个父组件将成为状态的持有者。
-
通过 props 将状态传递给子组件: 将状态作为 prop 传递给需要访问它的子组件。
-
在父组件中处理状态更新: 在父组件中定义状态的更新函数,并将这个函数作为 prop 传递给子组件。子组件可以调用这个函数来请求状态更新。
以下是一个简单的示例,说明如何在 React 中执行状态提升:
import React, { useState } from 'react';
// 子组件
function ChildComponent(props) {
return (
<div>
<p>Child Component: {props.value}</p>
<button onClick={props.onIncrement}>Increment</button>
</div>
);
}
// 父组件
function ParentComponent() {
// 定义状态
const [count, setCount] = useState(0);
// 处理状态更新的函数
const handleIncrement = () => {
setCount(count + 1);
};
// 渲染父组件和子组件,并将状态和更新函数通过 props 传递给子组件
return (
<div>
<p>Parent Component: {count}</p>
<ChildComponent value={count} onIncrement={handleIncrement} />
</div>
);
}
// 在应用中使用父组件
function App() {
return (
<div>
<ParentComponent />
</div>
);
}
export default App;
在这个例子中,ParentComponent
持有状态 count
,并通过 ChildComponent
将它的值和更新函数传递给子组件。当点击子组件中的按钮时,父组件的状态将得到更新,然后通过 props 更新到子组件。这样就实现了状态在父子组件之间的共享和同步。
2React可以在render访问refs吗?为什么?
在 React 中,ref
是用于访问组件实例或 DOM 元素的方式。通常,你可以在 componentDidMount
和 componentDidUpdate
生命周期方法中访问 refs,因为在这些生命周期方法中,组件已经被渲染到 DOM 中。
在 render
方法中直接访问 ref
是不推荐的,并且可能会导致错误。这是因为在 render
方法执行时,组件可能尚未完全渲染到 DOM 中,因此 refs 可能尚未被正确地分配。
如果你尝试在 render
方法中访问 ref
,你可能会得到 undefined
或者访问到一个尚未完全初始化的对象。这是因为 render
方法的目标是返回一个描述组件在特定时间点应该呈现的 React 元素,而不是进行与 DOM 或组件实例直接相关的操作。
如果你需要在组件挂载后或更新后访问 ref
,可以使用 componentDidMount
和 componentDidUpdate
这两个生命周期方法。此外,React 16.3 版本引入了 React.createRef
和 forwardRef
,提供了更灵活的方式来处理 refs。你可以在构造函数中使用 React.createRef()
创建 ref,然后将它传递给 JSX 中的元素。然后,你可以在 componentDidMount
、componentDidUpdate
或其他方法中访问该 ref。以下是一个简单的示例:
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
// 在组件挂载后可以访问 ref
console.log(this.myRef.current);
}
render() {
return <div ref={this.myRef}>Hello, React!</div>;
}
}
export default MyComponent;
在这个示例中,this.myRef
是通过 React.createRef()
创建的 ref,它在 componentDidMount
中被访问。
3React如何获取组件对应的DOM元素
在 React 中,你可以使用 ref
来获取组件对应的 DOM 元素。有两种方式来使用 ref
获取 DOM 元素:
-
回调形式的 Refs: 通过在组件中定义一个回调函数,React 会在组件挂载( componentDidMount
)或卸载(componentWillUnmount
)时将 DOM 元素作为参数传递给这个回调函数。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = null;
}
componentDidMount() {
// 在组件挂载后,this.myRef 将引用到相应的 DOM 元素
console.log(this.myRef);
}
render() {
return <div ref={(ref) => (this.myRef = ref)}>Hello, React!</div>;
}
}
-
React.createRef: 使用 React.createRef
创建一个 ref 对象,然后将这个 ref 对象传递给组件的ref
属性。这样,你就可以通过this.myRef.current
来访问 DOM 元素。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
// 在组件挂载后,this.myRef.current 将引用到相应的 DOM 元素
console.log(this.myRef.current);
}
render() {
return <div ref={this.myRef}>Hello, React!</div>;
}
}
选择哪种方式取决于你的个人偏好和 React 版本。使用回调形式的 Refs 对于函数组件和类组件都适用,而 React.createRef
主要用于类组件。在函数组件中,你也可以使用 useRef
钩子来创建 ref。
无论使用哪种方式,确保在访问 ref
时,组件已经被正确挂载,即在 componentDidMount
或类似的生命周期方法中进行访问。
4A组件嵌套B组件,生命周期执行顺序是什么
在 React 中,组件的生命周期包括挂载阶段(Mounting)、更新阶段(Updating)、和卸载阶段(Unmounting)。下面是组件A嵌套组件B时的生命周期执行顺序:
假设组件A包含组件B:
-
挂载阶段(Mounting):
-
A组件的 constructor
被调用。 -
A组件的 render
方法被调用。 -
B组件的 constructor
被调用。 -
B组件的 render
方法被调用。 -
B组件的 componentDidMount
被调用。 -
A组件的 componentDidMount
被调用。 -
更新阶段(Updating):
-
如果A组件的 setState
或父组件传递的props发生变化,A组件的shouldComponentUpdate
被调用。如果返回true
,则继续更新,否则停止更新。 -
如果A组件继续更新,A组件的 render
方法被调用。 -
如果B组件的 componentWillReceiveProps
(在React 16.3之前的版本)或getDerivedStateFromProps
(在React 16.3及之后的版本)被调用。 -
如果B组件的 shouldComponentUpdate
被调用,如果返回true
,则继续更新,否则停止更新。 -
如果B组件继续更新,B组件的 render
方法被调用。 -
如果B组件的 componentDidUpdate
被调用。 -
如果A组件的 componentDidUpdate
被调用。 -
卸载阶段(Unmounting):
-
如果A组件从DOM中移除,A组件的 componentWillUnmount
被调用。 -
如果B组件从DOM中移除,B组件的 componentWillUnmount
被调用。
需要注意的是,React 17及之后的版本中,componentWillReceiveProps
已经被废弃,推荐使用getDerivedStateFromProps
来替代。另外,React 16.3及之后的版本中,生命周期方法componentWillMount
、componentWillReceiveProps
、componentWillUpdate
也被标记为过时,推荐使用新的生命周期方法。
5diff和key之间的联系
diff算法即差异查找算法,对于DOM结构即为tree的差异查找算法,只有在React更新阶段才会有Diff算法的运用;react的diff运算为了降低时间复杂度,是按层比较新旧两个虚拟dom树的。
diff运算的主要流程见下:
-
tree diff : 新旧两棵dom树,逐层对比的过程就是 tree diff, 当整棵DOM树逐层对比完毕,则所有需要被按需更新的元素,必然能够被找到。 -
component diff : 在进行tree diff的时候,每一层中,都有自己的组件,组件级别的对比,叫做 component diff。如果对比前后,组件的类型相同,则暂时认为此组件不需要更新;如果对比前后,组件的类型不同,则需要移除旧组件,创建新组件,并渲染到页面上。
React只会匹配类型相同的组件,也就是说如果<A>被<B>替换,那么React将直接删除A组件然后创建一个B组件;如果某组件A转移到同层B组件上,那么这个A组件会先被销毁,然后在B组件下重新生成,以A为根节点的树整个都被重新创建,这会比较耗费性能,但实际上我们很少跨层移动dom节点,一般都是同层横向移动;
-
element diff :在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这叫做element diff。
对于列表渲染,react会在创建时要求为每一项输入一个独一无二的key,这样就能进行高效的diff运算了。比如我们要在b和c节点中间插入一个节点f,jquery会将f这个节点后面的每一个节点都进行更新,比如c更新成f,d更新成c,e更新成d,这样操作的话就会特别多,而加了key的react咋不会频繁操作dom,而是优先采用移动的方式,找到正确的位置去插入新节点;所以我们不能省略key值,因为在对比两个新旧的子元素是,是通过key值来精确地判断两个节点是否为同一个,如果没有key的话则是见到谁就更新谁,非常耗费性能。
当我们通过this.setState()改变数据的时候,React会将其标记为脏节点,在事件循环的最后才会重新渲染所有的脏节点以及脏节点的子树;另外我们可以使用shouldComponentUpdate这个生命周期来选择性的渲染子树,可以基于组件之前的状态或者下一个状态来决定它是否需要重新渲染,这样的话可以组织重新渲染大的子树。
6列举重新渲染render的情况
在 React 中,render
方法是负责渲染组件的核心方法。组件会在以下情况下触发重新渲染:
-
组件状态变化: 当使用 setState
方法修改组件的状态时,React 会触发重新渲染,调用render
方法。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Increment</button>
</div>
);
}
}
-
属性变化: 当组件的 props
发生变化时,也会触发重新渲染。
class MyComponent extends React.Component {
render() {
return <p>{this.props.message}</p>;
}
}
// 父组件中的使用
<MyComponent message="Hello, React!" />;
-
强制重新渲染: 通过 forceUpdate
方法可以强制组件进行重新渲染。通常情况下,推荐使用状态和属性管理组件的更新,而不是依赖于forceUpdate
。
class MyComponent extends React.Component {
handleClick = () => {
this.forceUpdate();
};
render() {
return (
<div>
<p>Random Value: {Math.random()}</p>
<button onClick={this.handleClick}>Force Update</button>
</div>
);
}
}
-
父组件重新渲染: 如果一个组件的父组件重新渲染,子组件也会跟着重新渲染。
-
使用 Hooks 中的
useState
和useReducer
: 在函数组件中使用useState
和useReducer
时,状态的更新也会触发重新渲染。
import React, { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
这些是触发 React 组件重新渲染的一些常见情况。每次触发重新渲染时,React 将调用组件的 render
方法来生成新的虚拟 DOM,并将其与之前的虚拟 DOM 进行比较,然后更新实际的 DOM。
7React按需加载问题
在 React 中,按需加载通常是指将应用程序的代码按照需要分割成多个块,并在需要时动态加载这些块,从而减少初始加载时的资源大小,提高应用程序的性能。React 支持通过以下方式实现按需加载:
-
React.lazy 和 Suspense: React 16.6 版本引入了 React.lazy
函数,它使得组件能够按需加载。搭配Suspense
组件使用,可以实现在加载组件时显示一个加载指示器。
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</React.Suspense>
);
}
注意,`React.lazy` 只能用于默认导出的组件。如果你的组件使用命名导出(export named),你需要进行适当的调整。
-
React Loadable: 在 React.lazy 可用之前,React Loadable 是一个常用的第三方库,用于实现按需加载。
import Loadable from 'react-loadable';
const MyComponent = Loadable({
loader: () => import('./MyComponent'),
loading: () => <div>Loading...</div>,
});
function App() {
return <MyComponent />;
}
React Loadable 提供了更多的配置选项,可以更灵活地处理加载状态。
-
Webpack 动态导入: 如果你使用 Webpack,你也可以使用动态导入来实现按需加载。
function App() {
const handleClick = async () => {
const module = await import('./MyComponent');
// 使用 module 中导出的内容
};
return <button onClick={handleClick}>Load MyComponent</button>;
}
这种方法直接使用了 Webpack 的动态导入语法,可以在需要时异步加载模块。
按需加载可以显著提高应用程序的性能,特别是对于大型应用程序。通过仅在需要时加载代码块,可以减小初始加载的资源大小,加快应用程序的加载速度。
8React和Vue的diff事件复杂度从O(n^3)优化到O(n)
React 和 Vue 都使用了虚拟 DOM 和一种称为 “差异算法”(Diff Algorithm)的技术来提高页面更新的性能。最初的差异算法复杂度是 O(n^3),但通过一些优化,目前的实现已经达到了 O(n) 的水平。
React 的优化:
React 使用了一种叫做 “Reconciliation” 的算法来进行虚拟 DOM 的比较。在 React 16 版本之前,React 使用了三次遍历的算法,复杂度为 O(n^3)。但在 React 16 版本中,React 团队引入了 “Fiber” 架构,通过使用一种叫做 “Fiber Reconciler” 的新算法,将复杂度降低到 O(n)。
“Fiber Reconciler” 使用了一种异步的、可中断的渲染方式,将任务分割成小单元,通过优先级调度,能够更细致地控制任务执行的顺序,从而提高渲染的效率。
Vue 的优化:
Vue 使用的是一种类似于 React 的 “Virtual DOM” 和 “差异算法” 的机制,但它在实现上有一些不同。
Vue 2 中使用的是一种叫做 “Virtual DOM Diff” 的算法,它也是一个 O(n) 复杂度的算法。Vue 3 引入了一种新的编译器和响应式系统,被称为 “Vue 3 Reactivity”。Vue 3 的虚拟 DOM diff 算法进行了优化,具有更高的性能。
总体而言,React 和 Vue 的 diff 算法都经历了多次的优化,使得复杂度从 O(n^3) 降低到了 O(n)。这使得它们在处理大规模页面更新时更加高效。
原文始发于微信公众号(前端大大大):React每日面试题
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/174080.html