1.虚拟 DOM 的原理是什么?
React 用 JS 对象来模拟 DOM 节点,然后将其渲染成真实的 DOM 节点,这个JS对象就是虚拟DOM。
用 JSX 语法写出来的『标签』就是JS对象的一种形式,后面会转换为JS对象。
如果数据发生变化,并不会直接render整个组件。而是通过对比JS对象得到真正发生变化的部分,仅对变化的部分调用DOM API进行修改。
优点:
结合diff算法,减少不必要的dom修改,保证性能。
为 React 带来了跨平台能力,因为虚拟节点除了渲染为真实节点,还可以渲染为其他东西。
React中的类组件和函数组件之间有什么区别?
函数组件无状态,没有state,没有生命周期。可以传入参数,类似于类组件的props。
在react16.8版本中添加了hooks,可以在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数。
什么是高阶组件(HOC)?
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
高阶组件是 React 中用于复用组件逻辑的一种技巧。
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
例如:React Router 的 withRouter,参数和返回值都是组件,用于给组件传入一些路由的API。
constructor中super与props参数一起使用的目的是什么?
ES6类:
如果在子类中不使用super关键字,会报错(报错内容:必须在访问this之前,或从constructor返回之前调用super())。
报错的原因是子类是没有自己的 this 对象的,它只能继承父类的 this 对象,然后对其进行加工。而 super() 就是将父类中的 this 对象继承给子类的,没有 super() 子类就得不到 this 对象。
类组件:
在调用 super()
的时候,我们一般都需要传入 props
作为参数,如果不传进去,React
内部也会将其定义在组件实例中。
但是也不建议使用 super()
代替 super(props)
,因为在 React
会在类组件构造函数生成实例后再给 this.props
赋值,所以在不传递 props
给 super
的情况下,constructor
中调用 this.props
为 undefined
。
总结:
在 React
中,类组件基于 ES6
,所以在 constructor
中必须使用 super
。
在调用 super
过程,无论是否传入 props
,React
内部都会将 porps
赋值给组件实例 porps
属性中。
如果只调用了 super()
,那么 this.props
在 constructor
中是 undefined
。
vue3js.cn/interview/R…
什么是受控组件?
受控组件是指由React组件负责管理其表单元素的状态的组件。在受控组件中,表单元素的值或状态由React组件的state来控制,而不是由DOM自身来管理。当用户与表单元素交互时,React组件会更新其state,从而更新表单元素的值或状态,并且通过事件处理程序来处理用户输入。
6.说说React的事件机制?
- React 的合成事件是 React 提供的一种事件处理系统,用于封装原生 DOM 事件,提供了跨浏览器兼容性的解决方案。
- 合成事件的优点在于它抽象了底层浏览器实现的差异,使得开发者不需要直接操作底层的事件对象,而是使用 React 提供的统一事件对象来处理事件。
- React 的合成事件还具有事件冒泡、事件代理、事件委托等功能。
- 减少了内存消耗,提升了性能。
- React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM,这减少了内存开销。
juejin.cn/spost/73432…
React中为什么要给组件设置 key?
可以复用一些只是位置发生变化而内容没变的dom元素。
旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变,直接使用之前的真实DOM。
②.若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
React Hooks带来了什么便利?
- 使得函数组件能使用state和生命周期方法。
- 更容易复用组件代码。
1.通过自定义Hooks,可以将一些共享的状态逻辑、副作用处理逻辑等提取出来,简化组件代码,提高代码复用性。
2.采用Hooks编写的组件更接近纯函数,使得逻辑更容易复用和测试。
3.更符合函数式编程的思想,可以更容易地进行逻辑的拆分和组合。 - 更易于测试:由于 Hooks 是纯函数,因此更易于对逻辑进行单元测试,提高代码质量和可靠性。
为什么不能在 循环/条件判断/嵌套函数 中调用 hooks?
组件第一次执行多个useState时,赋值。后面再次执行时,能够记住并返回对应值,依赖Hook的调用顺序。
React Hook 只能在 React 函数组件的顶层调用,而不能在循环、条件或嵌套函数中调用。这是因为 React Hook 的设计和实现依赖于 React 的组件渲染过程。在函数组件中,React Hook 的调用顺序必须保持稳定。React 依赖于 Hook 的调用顺序来正确地管理组件的状态和效果。
useState的大致实现:
列举几个常见的 Hook?
- useState:类似类式组件中 this.state、setState 的功能。
- useEffect:它的本意是监听一些数据,当数据更新的时候做一些事情。可以模拟生命周期钩子componentDidMount、componentDidUpdate和componentWillUnmount。
- useRef:获取组件的真实节点。
import React, { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
const handleButtonClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleButtonClick}>Focus Input</button>
</div>
);
}
export default MyComponent;
- useContext:获取 context 对象,用来向组件树深层传递数据时的一个环节。
import React, { useContext } from 'react';
// 创建一个上下文对象
const MyContext = React.createContext();
// 在组件中使用上下文提供者
const MyComponent = () => {
return (
<MyContext.Provider value="Hello">
<ChildComponent />
</MyContext.Provider>
);
};
// 在子组件中使用 useContext() 访问上下文
const ChildComponent = () => {
const value = useContext(MyContext); // 这里的参数是 MyContext
return <div>{value}</div>; // 这里的 value 是来自 MyContext 的值
};
- useMemo:用于对计算昂贵的值进行记忆化处理,以提高性能。它接受一个函数和一个依赖项数组作为参数,并返回由该函数计算得出的值。在组件重新渲染时,useMemo 会缓存上一次计算的值,只有在依赖项数组发生变化时才会重新计算。
import React, { useMemo } from 'react';
function MyComponent({ a, b }) {
// 使用 useMemo 缓存计算结果
const result = useMemo(() => {
console.log('Calculating result...');
return a * b;
}, [a, b]); // 依赖项数组包含 a 和 b
return <div>Result: {result}</div>;
}
export default MyComponent;
- useCallback:用于记忆化函数,以避免在每次渲染时创建新的函数实例。从而减少不必要的重复渲染。它接受一个函数和一个依赖项数组作为参数,并返回一个记忆化后的函数。当依赖项数组中的任何一个值发生变化时,useCallback 返回的函数实例才会重新计算,否则将会复用上一次的函数实例。
import React, { useState, useCallback } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存回调函数
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // 空依赖项数组表示不依赖任何外部值
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
export default MyComponent;
11.useRef()有什么用?
在 React 中,用于在函数组件中创建可变的引用。它返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数。useRef
返回的 ref 对象在整个组件的生命周期内保持不变,即使组件重新渲染,也不会改变。
获取 DOM 元素的引用:您可以使用 useRef 来获取函数组件中的 DOM 元素的引用。通过将 ref 属性赋值为 useRef 创建的 ref 对象,您可以在组件中访问到该 DOM 元素。
import React, { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
export default MyComponent;
存储任意可变值:除了 DOM 元素的引用之外,useRef 还可以用于存储任意可变值。通过修改 useRef 返回的 ref 对象的 .current 属性,您可以在组件的重新渲染中保存和更新数据,而无需触发重新渲染。
这段代码中,times记录渲染次数,每次渲染次数加一,但是加一的操作又会导致重新渲染,从而陷入死循环。
let App = function App() {
const [name, setName] = useState('');
const [times, setTimes] = useState(0);
useEffect(() => {
setTimes(times => times + 1);
});
return (
<>
<input onChange={e => setName(e.target.value)} />
<div>my name is {name}</div>
<div>i rendered {times} times</div>
</>
);
}
简单介绍下React中的 diff 算法
- 首先对比两棵树的根节点
a. 如果根节点的类型改变了,比如 div 变成了 p,那么直接认为整棵树都变了,不再对比子节点。此时直接删除对应的真实 DOM 树,创建新的真实 DOM 树。
b. 如果根节点的类型没变,就看看属性变了没有
ⅰ. 如果没变,就保留对应的真实节点
ⅱ. 如果变了,就只更新该节点的属性,不重新创建节点。 - 然后同时遍历两棵树的子节点,每个节点的对比过程同上。
为什么不能用数组下标来作为react组件中的key?
1.key用来标记相同的条目,如果只是变换位置,让React可以识别,能够复用。如果用下标作为key,同一个条目的下标是变化的,起不到复用的作用。
2.而且可能导致两次渲染中不同的条目key相同,导致React重用不同的条目,从而出现意料之外的效果。(输入框内已输入内容,第二次渲染重用了此输入框)
React Fiber是什么?(可优化)
www.google.com.hk/search?q=wh…
blog.logrocket.com/deep-dive-r…
React中的路由懒加载是什么?原理是什么?
是什么:按需加载页面,而不是一次性请求所有页面。所有JS代码被打包到一起,会导致首次加载速度较慢,按需加载时性能和加载速度都更好。
如何使用:在 React 中,通常使用 import()、React.lazy() 和 Suspense 组件来实现路由懒加载。
引入组件方式的变化:
// 不使用 React.lazy
import OtherComponent from './OtherComponent';
// 使用 React.lazy
const OtherComponent = React.lazy(() => import('./OtherComponent'))
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
如上代码中,通过 import()、React.lazy 和 Suspense 共同一起实现了 React 的懒加载。
原理:关于import() 原理,在浏览器宿主环境中一个import()的参考实现如下:
function import(url) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
script.type = "module";
script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;
script.onload = () => {
resolve(window[tempGlobal]);
delete window[tempGlobal];
script.remove();
};
script.onerror = () => {
reject(new Error("Failed to load module script with URL " + url));
delete window[tempGlobal];
script.remove();
};
document.documentElement.appendChild(script);
});
}
结合上面的代码来看,webpack 通过创建 script 标签来实现动态加载的。
关于 Suspense 组件,Suspense 内部主要通过捕获组件的状态去判断如何加载, React.lazy 创建的动态加载组件具有 Pending、Resolved、Rejected 三种状态,当这个组件的状态为 Pending 时显示的是 Suspense 中 fallback 的内容,只有状态变为 resolve 后才显示组件。
juejin.cn/spost/73449…
fe.ecool.fun/topic/de046…
16.React有哪些性能优化的方法?
减少渲染的节点/降低渲染计算量(复杂度)
- 不要在渲染函数都进行不必要的计算,比如不要在渲染函数(render)中进行数组排序、数据转换、订阅事件等等。
- 虚拟列表。只渲染当前视口可见元素。
- 惰性渲染。初衷本质上和虚表一样,也就是说只在必要时才去渲染对应的节点。
避免重新渲染
- 不变的事件处理器。避免使用箭头函数形式的事件处理器, 例如:
<ComplexComponent onClick={evt => onClick(evt.id)} otherProps={values} />
- 简化 state。不是所有状态都应该放在组件的 state 中。如果需要组件响应它的变动, 才应该放到 state 中。这样可以避免不必要的数据变动导致组件重新渲染。
避免不必要的渲染:通过正确使用 shouldComponentUpdate(对于类组件)或 React.memo(对于函数组件)来避免不必要的组件重新渲染。
React 组件间怎么进行通信?
父=>子:props。
子=>父:父组件向子组件传递函数,由子组件调用,调用时传入值。
兄弟之间:以父组件作为中介,A传给父,修改了父的state值,父通过props传给B。
父组件=>后代组件:通过 useContext。
其他情况:使用全局资源管理工具,如redux等。
Redux 中异步的请求怎么处理?(待完成)
Redux 状态管理器和变量挂载到 window 中有什么区别?
Redux中的connect有什么作用?
useEffect 与 useLayoutEffect 有什么区别?(待完成)
下面函数组件的输出分别是什么?
先点击“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>
)
}
弹出的值是 1,页面显示的值是 2。原因:setValue会导致整个组件重新执行,每次都生成新的log函数,每次也都重新声明了变量value,所以每个log函数对应的value变量都是不同的,并不对应最新的value值。这种现象被称为“闭包陷阱”或者“Capture Value”。
使用 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>
)
}
useRef 每次 render 时都会返回同一个引用类型的对象。
overreacted.io/how-are-fun…
21.React中,父子组件的生命周期执行顺序是怎么样的?
总结:
- 在挂载阶段,首先会调用父组件的生命周期方法,然后是子组件的生命周期方法。
- 在更新阶段,也是先调用父组件的生命周期方法,然后是子组件的生命周期方法。
- 在卸载阶段,先调用子组件的
componentWillUnmount
方法,然后才是父组件的。
- 挂载阶段:父组件:constructor -> render -> componentDidMount,子组件:constructor -> render -> componentDidMount。
- 更新阶段:父组件:render -> componentDidUpdate,子组件:render -> componentDidUpdate。
- 卸载阶段:子组件:componentWillUnmount,父组件:componentWillUnmount。
React.memo() 和 useMemo() 的用法是什么?
React.memo 是 React 提供的一个高阶组件,用于优化函数组件的性能。它类似于类组件中的 shouldComponentUpdate 生命周期钩子,用于避免不必要的重新渲染。
当你将一个函数组件包裹在 React.memo 中时,React 会将该组件的 props 与前一次渲染时的 props 进行比较。只有在 props 发生变化时,才会重新渲染该组件。如果 props 没有发生变化,则会直接返回上一次渲染的结果,从而减少不必要的重复渲染。
import React from 'react';
const MyComponent = React.memo(function MyComponent({ prop1, prop2 }) {
// 渲染组件的逻辑
});
export default MyComponent;
useMemo:用于对计算昂贵的值进行记忆化处理,以提高性能。它接受一个函数和一个依赖项数组作为参数,并返回由该函数计算得出的值。在组件重新渲染时,useMemo 会缓存上一次计算的值,只有在依赖项数组发生变化时才会重新计算。
import React, { useMemo } from 'react';
function MyComponent({ a, b }) {
// 使用 useMemo 缓存计算结果
const result = useMemo(() => {
console.log('Calculating result...');
return a * b;
}, [a, b]); // 依赖项数组包含 a 和 b
return <div>Result: {result}</div>;
}
export default MyComponent;
总结:React.memo() 缓存组件,useMemo() 缓存函数的计算结果。
使用 React hooks 怎么实现类里面的所有生命周期?
class 组件 | Hooks 组件 |
---|---|
constructor | useState |
render | 函数本身 |
componentDidMount | useEffect 第二个参数为[] |
componentDidUpdate | useEffect 第二个参数为需要监视的 props 或 state |
componentWillUnmount | useEffect 里面返回的函数 |
shouldComponentUpdate | React.memo |
juejin.cn/post/684490… |
实现一个 useTimeout Hook??
如果直接在函数式组件中使用 setTimeout ,会遇到以下问题。
多次调用setTimeout。我们原本的目的是在页面渲染完3s后修改一下state,但是你会发现当state+1后,触发了页面的重新渲染,就会重新有一个3s的定时器出现来给state+1,既而变成了每3秒+1。
function App() {
const [state, setState] = useState(1);
setTimeout(() => {
setState(state + 1);
}, 3000);
return (
<div> {state} </div>
);
};
useTimeout 实现:
function useTimeout(callback, delay) {
const memorizeCallback = useRef();
useEffect(() => {
memorizeCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const timer = setTimeout(() => {
memorizeCallback.current();
}, delay);
return () => {
clearTimeout(timer);
};
}
}, [delay]);
};
说说React Router有几种模式,以及实现原理?
共同点:改变 url 且不让浏览器向服务器发送请求。
hash 模式:改变hash值并不会导致浏览器向服务器发送请求,浏览器不发出请求,也就不会刷新页面。hash 值改变,触发全局 window 对象上的 hashchange 事件。所以 hash 模式路由就是利用 hashchange 事件监听 URL 的变化,从而进行 DOM 操作来模拟页面跳转。
history模式:Html5的History可以监听到路径变化,还可以直接修改路径同时让页面不跳转。(需要后端将所有对页面的请求都返回index.html)
示例代码:
import React from 'react';
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from './pages/Home';
import Login from './pages/Login';
import Admin from './pages/Admin';
function App() {
return (
<Router>
<Route path="/login" component={Login}/>
<Route path="/admin" component={Admin}/>
<Route path="/" component={Home}/>
</Router>
);
}
export default App;
26.说说你对React Router的理解?常用的Router组件有哪些?
Router的功能使得前端能够全面控制页面的切换,实现了页面代码的缓存和复用。这种方式可以提供更快的加载速度和更流畅的用户体验。
状态管理:在某些情况下,页面之间可能需要共享状态或数据。Router 功能提供了一种在不同页面之间共享状态和数据的方式,以便在整个应用程序中保持一致的用户体验。
路由参数和动态路由:有时需要将参数传递给页面,以便根据参数加载不同的内容。Router 功能允许您定义路由参数和动态路由,从而根据不同的 URL 加载不同的页面或组件。
常用的Router组件:
BrowserRouter、HashRouter:Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件。BrowserRouter是history模式,HashRouter模式。使用两者作为最顶层组件包裹其他组件。
Route:用于路径的匹配,然后进行组件的渲染。path 属性:用于设置匹配到的路径。component 属性:设置匹配到路径后,渲染的组件。
Link、NavLink:通常路径的跳转是使用Link组件,最终会被渲染成a元素。
Redirect:用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中。
Switch:作用是当匹配到第一个组件的时候,后面的组件就不应该继续匹配。
react-router还提供一些hooks:
useHistory:useHistory可以让组件内部直接访问history,无须通过props获取。
useParams:用于在函数组件中访问 URL 中的动态路由参数。
useLocation:返回当前 URL
的 location
对象。
react-router 里的 <Link> 标签和 <a> 标签有什么区别?
使用 <Link>
标签可以避免浏览器的页面刷新,因为它会使用 React Router 提供的路由机制进行导航,而不会发送新的 HTTP 请求。(背后是用history api修改url,通过react router切换页面组件)
使用 <a>
标签时,浏览器会向服务器发送新的 HTTP 请求,导致页面刷新。(相当于在地址栏输入地址并回车)