1.什么是React事件,什么是原生事件?两者的区别在哪儿?
- React 事件: React 事件是经过封装和合成的,以保证在不同浏览器上的一致性。在使用 React 中的事件处理时,你会给 JSX 元素添加事件处理函数,比如
onClick
、onChange
等,然后在事件处理函数中处理相应的逻辑。React 事件的处理方式提供了一定程度的抽象,使得开发者可以更加方便地处理事件和跨浏览器的兼容性。
function handleClick() { console.log('Button clicked'); } <button onClick={handleClick}>Click me</button>
- 原生事件是指由浏览器原生提供的事件,如
click
、change
、mouseover
等。在传统的 Web 开发中,我们通过 JavaScript 直接操作 DOM 元素并为其绑定原生事件处理程序来实现交互行为。
document.getElementById('myButton').addEventListener('click', function() { console.log('Button clicked'); });
区别:
- 合成事件系统:React 事件是经过合成的,React 通过事件委派(event delegation)的方式来管理事件,而不是直接将事件绑定到每个 DOM 元素上。这种方式可以提高性能,同时减少内存占用。
- 跨浏览器兼容性:React 事件处理封装了底层的浏览器差异,使得开发者无需关心不同浏览器的事件兼容性问题。
- 事件命名:React 使用驼峰式命名来定义事件名,如
onClick
、onChange
,而原生事件则使用全小写的方式,如click
、change
。
React 中的事件委派是指 React 将事件处理逻辑委托给组件的共同祖先(根组件),而不是直接在每个组件上添加事件监听器。这意味着 React 在整个组件树中只添加了一个事件监听器,而不是每个组件都有自己的事件监听器。 当用户触发事件时,React 会在 DOM 树中找到最近的共同祖先,并在该节点上触发事件处理函数。然后,React 会使用事件冒泡(event bubbling)的机制将事件传播到组件树中的每个组件,并调用相应的事件处理函数。 React 事件委派的优点包括: 性能优化:减少了事件监听器的数量,提高了性能。相比每个组件都添加事件监听器,只有一个共同祖先添加事件监听器的方式更加高效。 简化事件管理:不需要为每个组件都添加事件监听器,减少了事件管理的复杂性。当组件被添加、移除或更新时,不需要手动管理事件监听器。 更少的内存占用:只有一个事件监听器,减少了内存占用。
2.React的diff算法
React 的 Virtual DOM 和 diff 算法是其性能优势的关键所在。在 React 中,当 state 或 props 发生变化时,React 会通过 diff 算法比较新旧 Virtual DOM 树的差异,并只更新实际变化的部分,而不是重新渲染整个页面。
React 的 diff 算法主要包括以下几个步骤:
-
生成 Virtual DOM 树:当组件状态发生变化时,React 会重新构建 Virtual DOM 树。
-
Diffing 算法:React 使用 diff 算法比较新旧 Virtual DOM 树的差异。这一过程包括两个阶段:
-
深度优先搜索:React 会递归地遍历新旧节点,找出差异。如果节点类型不同,直接替换;如果节点类型相同,继续比较子节点。
-
对比子节点:React 会对比新旧节点的子节点列表,找出需要更新、删除或新增的节点。
-
-
更新 DOM:根据 diff 的结果,React 会计算出最小的变更,然后批量更新 DOM,以尽量减少对实际 DOM 的操作次数。
React 的 diff 算法的优势在于减少了不必要的 DOM 操作,提高了性能。通过只更新变化的部分,React 能够更高效地处理大型应用程序中的 UI 变化,同时保持页面的响应速度。
需要注意的是,尽管 React 的 diff 算法在大多数情况下表现良好,但也有一些特殊情况可能会导致性能问题,比如列表中的子元素顺序变化较大时。针对特定场景,开发者也可以通过一些手动优化措施来提升性能。
3.React的diff算法跟Vue的diff算法的区别?
React 的 diff 算法是基于 Fiber 架构的 Reconciliation(对账) 算法,采用深度优先的递归比较方式,使用 Key 属性和双端比较来处理 Virtual DOM 树的差异。
Vue 使用双端渲染和 Virtual DOM 的差异比较算法,通过节点索引和差异比较实现快速更新操作。
React 更灵活和精确,Vue 更简洁高效。选择取决于具体需求和偏好
4.React的生命周期?废弃了哪些?可替代的有哪些?生命周期到现在的一个历程?
在 React 16.3 版本之前,React 组件的生命周期方法包括以下几个阶段:
-
Mounting(挂载阶段):
- constructor()
- componentWillMount()(已废弃)
- render()
- componentDidMount()
-
Updating(更新阶段):
- componentWillReceiveProps()(已废弃)
- shouldComponentUpdate()
- componentWillUpdate()(已废弃)
- render()
- componentDidUpdate()
-
Unmounting(卸载阶段):
- componentWillUnmount()
-
Error Handling(错误处理阶段):
- componentDidCatch()
在 React 16.3 版本中,一些生命周期方法被标记为废弃,主要是因为它们可能会导致一些潜在的问题,或者在未来版本中可能会被移除。这些废弃的生命周期方法包括:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
替代方案包括:
- getDerivedStateFromProps():用于替代 componentWillReceiveProps 和 componentWillUpdate,允许根据 props 的变化来更新组件的 state。
- getSnapshotBeforeUpdate():用于替代 componentWillUpdate,允许在更新前获取 DOM 的快照信息。
React 16.8(引入了 Hooks)
- 引入了
useEffect
这一 Hook,用于替代类组件中的生命周期方法,以处理组件的副作用操作。 useEffect
可以在函数组件中执行副作用操作,并且可以在组件渲染后进行一些操作,类似于类组件中的生命周期方法。useEffect
也可以返回一个清除函数,用于清理副作用操作,避免内存泄漏和其他问题。
useEffect
主要替代了类组件中的 componentDidMount
, componentDidUpdate
, 和 componentWillUnmount
等生命周期方法,使得组件的副作用操作更加灵活和易于管理。
5.React常用的hook函数有哪些?如何自定义hook函数?
- useState:用于在函数组件中添加状态管理能力。
const [count, setCount] = useState(0); <button onClick={() => setCount(count + 1)}>{count}</button>
- useEffect:处理副作用操作,比如数据获取、订阅等。
useEffect(() => { // 这里的代码在组件挂载时执行 return () => { // 这里的代码在组件卸载时执行 }; }, []); // 空数组意味着没有依赖,只在组件挂载和卸载时执行
- useContext:让你在函数组件中使用 React 的 Context。
在 React 中,Context 是一种用于在组件之间共享数据的方法,而无需通过组件树的逐层传递 props。它可以帮助我们避免在多层嵌套的组件中通过 props 一层层地传递数据,特别是对于应用中许多组件都需要某些相同的数据时很有用。 使用 Context,可以创建一个“全局”数据存储,使得所有子组件都可以直接访问这些数据,而不必通过中间组件来传递。这在某些情况下能够简化组件之间的通信并提高代码的可维护性。
const ThemeContext = React.createContext('light'); // 在根组件中提供共享的数据 function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } // 在子组件中使用共享的数据 function Toolbar() { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return <button style={{ background: theme }}>I am styled by theme context!</button>; }
- useReducer:类似于 Redux 中的 reducer,用于复杂的状态管理。
// 定义 reducer 函数 const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } }; // 初始状态 const initialState = { count: 0 }; // 使用 useReducer function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> Count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); }
- useRef:获取 DOM 元素的引用或者保存任意可变值。
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` 指向已挂载到 DOM 上的文本输入元素 inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
- useMemo:用于性能优化,避免不必要的计算。
useMemo 的作用 1.性能优化:useMemo 可以缓存计算结果,在依赖项不变的情况下避免重复计算,从而提高性能。 2.避免不必要的渲染:当组件重新渲染时,如果某个值是通过 useMemo 缓存的,且依赖项没有发生变化,那么这个值不会重新计算,避免不必要的渲染。
function ExpensiveCalculation({ value }) { // 使用 useMemo 缓存计算结果 const result = useMemo(() => { console.log('Calculating...'); return value * 2; }, [value]); // 依赖项为 value return <div>Result: {result}</div>; } function App() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> <ExpensiveCalculation value={count} /> </div> ); }
- useCallback:用于性能优化,避免不必要的回调函数重复创建。
function ClickCounter({ onClick }) { return <button onClick={onClick}>Click Me</button>; } function App() { const [count, setCount] = useState(0); // 使用 useCallback 缓存回调函数 const handleClick = useCallback(() => { setCount(count + 1); }, [count]); // 依赖项为 count return ( <div> <p>Count: {count}</p> <ClickCounter onClick={handleClick} /> </div> ); }
- useLayoutEffect:类似于 useEffect,但会在 DOM 变更之后同步触发效果。
useLayoutEffect 是 React 提供的一个 Hook,与 useEffect 类似,用于在组件渲染到 DOM 之后执行副作用。但是,useLayoutEffect 会在所有 DOM 变更之后同步调用副作用函数,以确保 DOM 变更同步更新。 适合使用场景 1.需要在 DOM 更新后立即执行操作,如测量 DOM 元素的尺寸、位置等。 2.需要在布局计算之后立即执行操作,以避免闪烁或布局问题。
function WidthMeasure() { const [width, setWidth] = useState(0); const divRef = useRef(); useLayoutEffect(() => { setWidth(divRef.current.clientWidth); }, []); // 空依赖项,只在组件挂载和更新时执行 return ( <div> <div ref={divRef}>Measure my width</div> <p>Width: {width}px</p> </div> ); }
6.hook函数的优缺点?
优点:
-
代码复用:Hook 函数使得在函数组件中重用状态逻辑变得更容易。
-
组合性:Hook 函数之间可以自由组合,使得逻辑组织更加灵活。
-
清晰简洁:将相关逻辑分离到不同的函数中,使得代码清晰易懂。
-
无需关注 this 指针:避免了类组件中常见的 this 指针问题。
-
轻量化:相比类组件,函数组件通常更轻量,性能更好。
缺点:
-
学习曲线:需要一定时间来理解和掌握 Hook 函数的概念。
-
破坏性改变:改变了传统的 React 编程习惯,可能需要适应和转变思维。
-
限制:一些生命周期方法和特性在函数组件中无法直接使用。
-
闭包陷阱:需要注意闭包问题,避免意外行为。
7.React组件怎么做事件代理?
事件代理(Event Delegation)是一种利用事件冒泡机制来处理事件的技术。在浏览器中,当一个元素上触发了某个事件时(比如点击事件),这个事件会沿着 DOM 树向上传播,直到根节点。利用事件冒泡,我们可以在父元素上监听事件,然后通过判断具体触发事件的子元素来执行相应的逻辑,从而实现事件代理。
在 React 中,事件代理的原理与浏览器中的事件冒泡机制是一致的。当子组件上的事件被触发时,事件会一层层向上传播,直至父组件或更高层级的祖先组件。通过在父组件上设置事件监听,并在事件处理函数中判断触发事件的具体元素,就能实现事件代理。
具体步骤如下:
- 在父组件上添加事件监听函数。
- 事件触发后,事件会沿着 DOM 树向上传播。
- 在事件处理函数中,可以通过
event.target
获取触发事件的具体元素。 - 根据
event.target
的信息,执行相应的逻辑。
通过这种方式,可以减少在子组件上编写大量重复的事件处理函数,提高代码的可维护性和性能表现。同时,利用事件代理还可以动态地管理子组件中的事件,更灵活地处理用户交互。
8.React的高阶组件(HOC)是什么?
React 的高阶组件(Higher-Order Component,HOC)是一种用于复用组件逻辑的高级技术。它本质上是一个函数,接受一个组件作为输入,并返回一个新的组件。通过使用高阶组件,可以在不修改现有组件代码的情况下,添加额外的功能、状态或逻辑。
高阶组件的基本特点和用法:
-
接受组件作为参数:高阶组件接受一个组件作为参数,通常以组件作为参数的函数形式实现。
-
返回新的组件:高阶组件内部会对传入的组件进行包装或者修改,最终返回一个新的组件。
-
复用逻辑:通过高阶组件,可以将一些通用的逻辑、状态管理、数据获取等功能提取出来,并应用到多个组件中,实现逻辑的复用。
-
可组合:可以通过组合多个高阶组件,实现更复杂的功能扩展和逻辑复用。
-
与原始组件解耦:通过高阶组件,可以将与组件逻辑无关的功能(例如数据获取、权限控制等)与原始组件逻辑分离,提高了组件的复用性和可维护性。
使用高阶组件的常见场景包括但不限于:状态管理、数据获取、条件渲染、权限控制、事件处理等。
9.React.createClass和 extends Component的区别?
- React.createClass:
React.createClass
是 React 早期版本提供的一种创建组件的方式。- 使用
React.createClass
创建组件时,可以直接定义组件的配置对象,其中包括组件的状态、生命周期方法等。 - 不需要手动绑定 this,因为在 React.createClass 中,函数会自动绑定到实例上。
- 不支持 ES6 类的特性,比如无法使用类属性语法(class properties)等新特性。
const MyComponent = React.createClass({ render: function() { return <div>Hello, World!</div>; } });
- extends Component:
extends Component
是 ES6 类的语法,通过继承React.Component
类来创建 React 组件。- 使用 ES6 类语法创建组件更符合现代 JavaScript 的标准,也更容易理解和维护。
- 需要手动绑定 this,或者使用箭头函数来避免手动绑定问题。
- 支持 ES6 类的特性,可以使用更多现代 JavaScript 的语法特性。
class MyComponent extends React.Component { render() { return <div>Hello, World!</div>; } }
10.React根据什么判断什么时候重新渲染组件?
在 React 中,组件何时重新渲染是由 React 的 Virtual DOM 和 Reconciliation(对账) 来决定的。React 使用 Virtual DOM 来跟踪页面上的真实 DOM 树,并使用协调算法来确定何时以及如何更新组件。
当组件的状态(state)或属性(props)发生变化时,React 会触发组件的重新渲染。具体来说,React 对比前后两次渲染所生成的 Virtual DOM 树,找出两者之间的差异(Diff),然后只更新必要的部分到真实 DOM 中,以尽量提高性能和效率。
以下是一些触发组件重新渲染的情况:
-
setState() 方法被调用:当调用组件的
setState()
方法时,React 会重新计算组件的 Virtual DOM 树,并与之前的 Virtual DOM 进行对比,从而确定需要更新的部分。 -
props 改变:当父组件传递给子组件的 props 发生变化时,子组件会重新渲染以反映最新的 props 值。
-
forceUpdate() 方法被调用:通过调用组件的
forceUpdate()
方法可以强制组件重新渲染,不管组件的状态或属性是否有变化。 -
父组件重新渲染:如果一个组件的父组件重新渲染,那么子组件也会相应地进行重新渲染。
值得注意的是,React 会尽量减少不必要的重新渲染,因此在实际开发中,我们可以通过 shouldComponentUpdate 生命周期方法或 PureComponent、React.memo 等方式来优化组件的性能,避免不必要的渲染。
11.React可以在哪个生命周期访问DOM,在哪个时机访问Ref?
12.Prop是什么?state是什么?state和Prop的区别?
13.setState做了什么操作?异步还是同步?
14.React为什么要校验Prop?
15.Redux是什么?描述一下Redux的工作流程?
16.Redux和VueX的区别?
17.React组件中的key如何理解?
18.React组件通讯的方式有哪些?
标签:面试题,return,函数,DOM,更新,React,事件,组件 From: https://www.cnblogs.com/qinlinkun/p/18102518