]kk前端全部面试题
React面试题
1.react hook的好处和限制是什么? (考)
好处:
- 代码简洁:将相同逻辑的代码放到一个函数中,代码看起来更简洁
- 逻辑复用:Hook可以让逻辑代码得到模块化,方便进行复用
- 兼容性好:Hook不改变原来的React API
- 可以更好的处理逻辑,减少react生命周期钩子的使用
限制:
- 只能在函数组件和自定义 Hook 中使用,不适用于类组件
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
2.常用的hook(考)
- useState:用于在函数组件中使用state;
- useEffect:用于在组件渲染完成后执行副作用操作(比如数据获取、事件监听等);
- useContext:用于在组件中访问React上下文;
- useMemo:用于缓存计算结果,优化性能;
- useCallback:用于缓存函数引用,优化性能;
- useRef:用于在函数组件中使用ref;
- useReducer:类似于Redux中的reducer,用于管理复杂的状态逻辑。
3.react diff
diff算法:用来计算VirtualDOM中被改变部分,针对该部分进行原生DOM操作,不用重新渲染整个页面,够高效地更新视图,减少不必要的 DOM 操作,提升页面性能
Diff 算法主要包括以下几个步骤:(不用看)
- 比较不同类型的元素:如果新旧元素的类型不同,直接销毁旧元素,创建并插入新元素。
- 同一类型元素的处理:如果新旧元素类型相同,则进行属性的更新和子元素的比较。
- 更新元素属性:遍历新旧元素的属性,找出变化的属性并更新到 DOM 上。
- 更新子元素:递归比较新旧元素的子元素,找出变化并更新到 DOM 上。
- 列表元素的处理:对列表元素进行 Diff 操作时,React 使用 key 属性来标识列表中每个元素的唯一性,以便正确识别新增、删除或移动的元素。
原理:
-
比较节点类型: 首先比较新旧两个节点的类型,如果类型不同,则认为节点已经发生了变化,需要替换整个节点及其子节点。
-
比较节点属性: 如果新旧节点的类型相同,会比较节点的属性。遍历新旧节点的属性列表,比较属性值是否相同。如果属性值不同,则需要更新该属性。
-
递归比较子节点: 如果新旧节点的类型和属性都相同,会递归比较它们的子节点。这样就可以找出子节点列表中发生变化的节点,并进行相应的更新操作。
-
使用唯一 key 进行优化: 在列表渲染时,给每个列表元素添加一个唯一的 key 属性。在 Diff 算法中,用这个 key 来区分列表中的元素,并尽可能复用已有的 DOM 节点,减少创建和销毁节点的开销。
通过几个步骤,Diff 能够快速且准确地找出新旧虚拟 DOM 树之间的差异。然后,React 只需要对发生变化的部分进行实际 DOM 操作,而不是全量更新整个页面,从而提高性能。
4.React 中 refs 的作用是什么?
在 React 中,refs 是用来获取组件实例或 DOM 元素的引用的方法。使用 refs 可以让我们直接访问到组件实例或者 DOM 元素,从而可以对其进行操作,比如获取输入框的值、设置焦点、播放视频等操作。
refs 在 React 中通常用于以下几种情况:
- 获取 DOM 元素的引用:可以通过 refs 获取某个 DOM 元素,然后直接操作这个 DOM 元素,比如修改样式、添加事件监听器等。
- 控制子组件:可以通过 refs 获取子组件的实例,然后调用子组件暴露出的方法或属性,实现父子组件之间的通信。
- 表单控件获取值:可以通过 refs 获取表单元素(比如 input、textarea)的值,而不必每次都通过 state 来获取。
需要注意的是,在函数组件中使用 refs 有一些限制,通常推荐在类组件中使用 refs。在函数组件中可以使用 useRef() 钩子来创建一个 ref 对象来获取 DOM 元素的引用。
// 类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 对象
this.myRef = React.createRef();
}
componentDidMount() {
// 在组件挂载后,可以通过 this.myRef.current 来获取 DOM 元素的引用
console.log(this.myRef.current);
}
render() {
return <div ref={this.myRef}>Hello, World!</div>;
}
}
// 函数组件
import React, { useRef, useEffect } from 'react';
function MyComponent() {
const myRef = useRef();
useEffect(() => {
// 在组件挂载后,可以通过 myRef.current 来获取 DOM 元素的引用
console.log(myRef.current);
}, []);
return <div ref={myRef}>Hello, World!</div>;
}
6.虚拟DOM的原理
React 中的虚拟 DOM(Virtual DOM)相当于 DOM 拷贝,它是 React 对实际 DOM 的抽象表示。虚拟 DOM 的原理可以简单概括为以下几个步骤:
-
初始化阶段: 当 React 组件首次渲染时,会构建一棵虚拟 DOM 树,这棵树包含了组件的结构、属性和内容等信息。
-
状态变化阶段: 当组件的状态发生变化时,React 不直接操作实际 DOM,而是创建一个新的虚拟 DOM 树来代表更新后的 UI 结构。
-
Diff 阶段: React 使用一种高效的算法(即 Diff 算法)比较新旧两棵虚拟 DOM 树的差异,找出需要更新的部分。
-
更新阶段: 最终,React 会根据 Diff 的结果,只对需要更新的部分进行实际 DOM 操作,从而最小化页面重绘和重新排版的开销,提高性能。
为什么虚拟DOM会提高性能 ?
通过使用虚拟 DOM,React 在内存中维护 DOM 树,避免了直接频繁操作实际 DOM 带来的性能损耗。虚拟 DOM 可以减少实际 DOM 操作的次数,并通过 Diff 算法优化更新过程,使得页面的重新渲染更加高效。
总的来说,虚拟 DOM 的原理是通过在内存中构建虚拟 DOM 树来代表 UI 结构,结合 Diff 算法来比较新旧虚拟 DOM 树的差异,并最终更新实际 DOM,从而实现高效的页面更新和性能优化。这一机制使得 React 能够更快速地响应用户操作,提升用户体验。
代码:
function diff(oldVirtualDOM, newVirtualDOM) {
// 若新旧节点完全相同,则无需进行进一步比较
if (oldVirtualDOM === newVirtualDOM) {
return;
}
// 若节点类型不同,则直接替换整个节点
if (typeof oldVirtualDOM !== typeof newVirtualDOM || oldVirtualDOM.type !== newVirtualDOM.type) {
replaceNode(oldVirtualDOM, newVirtualDOM);
return;
}
// 更新节点属性
updateNodeAttributes(oldVirtualDOM.props, newVirtualDOM.props);
// 比较子节点
const oldChildren = oldVirtualDOM.props.children;
const newChildren = newVirtualDOM.props.children;
if (typeof newChildren === 'string' || typeof newChildren === 'number') {
// 子节点为文本节点时,直接更新文本内容
if (oldChildren !== newChildren) {
updateTextNode(oldVirtualDOM, newVirtualDOM);
}
} else {
// 子节点为数组时,逐个比较子节点
const oldLength = oldChildren.length;
const newLength = newChildren.length;
for (let i = 0; i < Math.max(oldLength, newLength); i++) {
diff(oldChildren[i], newChildren[i]);
}
// 处理删除的子节点
if (oldLength > newLength) {
for (let i = newLength; i < oldLength; i++) {
removeNode(oldChildren[i]);
}
}
// 处理新增的子节点
if (newLength > oldLength) {
for (let i = oldLength; i < newLength; i++) {
appendNode(newChildren[i]);
}
}
}
}
// 替换节点
function replaceNode(oldNode, newNode) {
const parentNode = oldNode.parentNode;
parentNode.replaceChild(createDOM(newNode), createDOM(oldNode));
}
// 更新节点属性
function updateNodeAttributes(oldProps, newProps) {
// 比较新旧属性,更新变化的属性值
}
// 更新文本节点内容
function updateTextNode(oldNode, newNode) {
oldNode.nodeValue = newNode.props.children;
}
// 移除节点
function removeNode(node) {
node.parentNode.removeChild(createDOM(node));
}
// 添加节点
function appendNode(node) {
parentNode.appendChild(createDOM(node));
}
7.React有哪些优化性能的手段 (4个即可)
-
使用 Virtual DOM 和 Diff 算法:用 Virtual DOM 和 Diff 算法来比较新旧 DOM 结构的差异,最小化 DOM 操作,从而提高性能。
-
避免不必要的重新渲染: 使用 PureComponent 或 shouldComponentUpdate 生命周期函数来避免不必要的组件重新渲染,只有在必要时才重新渲染组件。
-
列表元素添加唯一 key 属性: 在列表元素中添加唯一的 key 属性,正确识别列表元素的变化,避免不必要的 DOM 更新操作。
-
使用函数式组件和 React Hooks: 函数式组件和 React Hooks 相对于类组件具有更好的性能表现,推荐在新项目中使用函数式组件和 Hooks。
-
懒加载和代码分割: 使用 React.lazy() 和 Suspense 来进行组件的懒加载,以及使用动态 import() 进行代码分割,减少首次加载时的资源消耗。
-
使用 useMemo 和 useCallback: 使用 useMemo 缓存计算结果,避免重复计算;使用 useCallback 缓存回调函数,避免每次都生成新的回调函数。
-
合理使用 Context 和 Provider: 避免过度使用 Context 和 Provider,因为 Context 的更新会导致所有依赖该 Context 的组件重新渲染。
-
性能监控和调试工具: 使用 React 开发者工具等工具来监控组件性能、检测性能瓶颈,并及时进行优化。
8.说一下
useMemo
和useCallback
有什么区别(考)
useMemo:返回一个缓存值,可以是任意类型的值,只要依赖项列表中的某个值改变时,才会重新计算缓存的值
useCallback:返回一个缓存回调函数,只有依赖项列表中的某个值发生改变时,才会重新创建回调函数。用来避免每次渲染时创建新的回调函数。
9.你知道
useEffect
第二个参数内部是怎么比较的吗? (考)
时机不同:
- useEffect:是在浏览器绘制或渲染完成之后执行
- useLayoutEffect是在浏览器绘制或渲染之前执行
实现不同:
- useEffect:使用“异步执行”方式,在浏览器完成“绘制任务树” 后才“异步执行”设置的函数
- useLayoutEffect则使用“同步执行”的方式,会在执行useLayoutEffect的回调函数时阻塞浏览器,直到回调函数执行完成才会继续进行浏览器的绘制和渲染
对于 useEffect
第二个参数,它是一个依赖数组,包含了所有需要被监视的变量。当依赖数组中的变量发生了变化,浏览器会在下一次渲染前执行该回调函数。
判断两个 Javascript 值是否相等的方法有很多,但是在 useEffect
中执行的比较是使用了 Object.is() 来进行比较的。
Object.is() 是 ES6 中的一个方法,用于比较两个 Javascript 数据类型的值是否相同。与相等运算符(==、===)不同的是,它认为 NaN 等于 NaN,而且 +0 和 -0 不相等。这种相等方式对于 React 的应用场景来说是正确的,因为它更符合 Javascript 中的相等判断语义。
10.问了一下
useEffect
对应在 class 中都生命周期怎么写? (不用看)
在 class 中,不同的生命周期对应了 useEffect 的不同用法:
- componentDidMount 对应 useEffect 的第二个参数为空数组 [],表示只在组件挂载时执行一次。
- componentDidUpdate 对应 useEffect 的第二个参数不为空数组,表示仅在依赖项发生变化时执行。
- componentWillUnmount 对应 useEffect 的返回值函数,用于清除副作用。
// 可以将 componentDidMount 写成:
componentDidMount() {
// 执行副作用
// ...
}
// 对应的 useEffect 写法
useEffect(() => {
// 执行副作用
// ...
// 清除副作用
return () => {
// 清除操作
// ...
}
}, []); // 第二个参数为空数组,表示只在组件挂载时执行一次
// componentDidUpdate 可以这样写
componentDidUpdate(prevProps, prevState) {
if (this.props.someProp !== prevProps.someProp) {
// 执行副作用
// ...
}
}
// 对应的 useEffect 写法
useEffect(() => {
// 执行副作用
// ...
// 清除副作用
return () => {
// 清除操作
// ...
}
}, [props.someProp]); // 只在 props.someProp 发生变化时执行
// componentWillUnmount 可以这样写
componentWillUnmount() {
// 清除副作用
// ...
}
// 对应的 useEffect 清除副作用写法
useEffect(() => {
// 执行副作用
// ...
// 清除副作用
return () => {
// 清除操作
// ...
}
}, []); // 第二个参数为空数组,表示只在组件挂载时执行一次
如果在 if 里面写
useEffect
会有什么表现? (不用看)
在 useEffect
的第二个参数内部比较是通过使用 Object.is
来比较。每次更新状态时,useEffect
的第二个参数会与上一个渲染周期内传递的参数进行比较,如果两次传递的参数相等(使用 Object.is
比较),则不会触发 useEffect
回调,否则会触发 useEffect
回调。可以理解为,第二个参数的作用是告诉 useEffect
何时会发生变化,从而触发回调。
如果在 if
里面写 useEffect
,则 useEffect
函数会被多次调用。这是因为在函数组件渲染时,if
语句的条件可能在不同的渲染周期中发生变化,导致 useEffect
会被多次调用。这可能会导致副作用操作被多次执行,从而引起性能问题或其他问题。
为了避免这种问题,可以使用 useEffect
的第二个参数,指定依赖项列表,只有当依赖项列表中的值发生变化时才会触发 useEffect
回调,从而避免不必要的重新渲染和副作用操作。这样,即使在 if
语句中使用 useEffect
,也可以显式地指定依赖项,避免出现多次执行的问题。
说一下 React 的
Fiber
架构是什么 (不用看)
React Fiber 是 React v16 中引入的一种新的协调算法,主要为了解决 React 之前版本中的性能问题。在 React Fiber 架构下,React 可以进行更细粒度的控制。它将组件的更新过程分成了两个阶段reconciliation 阶段和 commit 阶段。其中 reconciliation 阶段主要是进行 fiber tree 的构建与 fiber tree diff,commit 阶段主要是执行实际的 DOM 操作和事件处理等副作用操作。
React Fiber 架构相比较之前的版本,引入的最大的变化是增加了 Fiber 节点(Fiber Node)。每个组件实例都对应有一个 Fiber 节点,这个节点存储了组件的状态、对应的 DOM 元素以及其他一些调度 React 进行更新所需的元数据。Fiber 是一个链表结构,并且 React 可以将这个链表分成多个子链表进行处理,从而可以更灵活地控制执行上下文(执行栈)和任务的优先级,达到更高的性能表现。换句话说,Fiber 架构使得 React 可以更细粒度地控制组件的更新、渲染和交互等过程,在 UI 响应速度和性能优化等方面有了很大的改进。
总而言之,React Fiber 架构是 React v16 引入的一种更高效的更新机制,它使 React 更加灵活和高效,大幅提升了 React 的性能,同时改进了 React 的可交互性。
11.React有什么特点?(react的主要功能有哪些?)
它用于虚拟DOM,组件化设计模式,声明式代码,单向数据流,使用jsx描述信息等特点
12.介绍Redux数据流的流程 (重要)(考)
Redux如何实现多个组件之间的通信,多个组件使用相同状态如何进行管理
redux是js状态容器
工作流程:
- View在redux中会派发action方法
- action通过store的dispatch方法会派发给store
- store接收action,连同之前的state,一起传递给reducer
- reducer返回新的数据给store
- store去改变自己的state
好处:
- 集中管理应用状态,使状态变化更可控;
- Redux将所有状态统一保存在全局变量中,使不同组件之间可以方便地共享数据。
- 保证组件的状态和逻辑与其生命周期解耦,使得组件更容易复用,维护
api:
- store.dispatch方法,触发action
- store.getState,获取当前状态
- store.subscribe,监听状态改变
import { createStore, createAction, createReducer } from 'redux';
// 创建 Action
const increment = createAction('INCREMENT');
// 创建 Reducer
const counterReducer = createReducer(0, {
[increment]: state => state + 1
});
// 创建 Store
const store = createStore(counterReducer);
// 订阅 Store
store.subscribe(() => {
console.log(store.getState());
});
// 分发 Action
store.dispatch(increment());
什么时候使用状态管理器?
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 某个组件的状态需要共享
- 某个状态需要在任何地方都可以拿到
Redux——详解_Mr.指尖舞者的博客-CSDN博客_redux
Redux原理及工作流程_QcoY_的博客-CSDN博客_redux工作流程
13.React Hooks常用的 (必会)
useState
useState
UseState:在函数组件中定义状态,接收一个参数作为初始值,并返回一个数组。数组的第一个元素是当前状态的值,第二个元素是用于更新状态的函数
使用方法:
import React,{useState} from 'react'
function Counter() {
const [count,setCount] = useState(0)
return (
<div>
<p>Count:{count}</p>
<button onClick={() => setCount(count+1)}>Increment</button>
</div>
)
}
useEffect
useEffect
是在浏览器绘制完成后执行,而 useLayoutEffect
是在浏览器绘制前执行。这意味着,如果你在 useEffect
中操作 DOM,可能会看到页面闪烁,而使用 useLayoutEffect
可以避免这个问题。
useEffect接收两个参数,第一个参数是一个函数,表示需要执行的操作。第二个参数是一个数组,表示需要监视的状态值
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// 执行副作用操作,例如修改文档标题
document.title = `你点击了 ${count} 次`;
}, [count]);
return (
<div>
<p>你点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击增加</button>
</div>
);
}
如果第二个参数为空数组,则表示 useEffect 仅在组件渲染时执行一次,相当于类组件中的componentDidMount。如果省略第二个参数,则表示 useEffect 在每次组件渲染时都会执行。
useContext
useContext:它可以函数组件不需要通过props层层传递数据,直接访问全局数据
import React, { useContext } from 'react';
import UserContext from './UserContext';
function UserProfile() {
const { user } = useContext(UserContext);
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}
useRef
useRef:获取组件中元素,返回一个可变的对象
function TextInput(){
const inputRef = useRef(null)
const onButton = () => {
inputRef.current.focus()
inputRef.current.select()
}
return (
<>
<input ref={inputRef} type="text"/>
<button variant="outlined" onClick={onButton}>点击</button>
</>
)
}
useMemo/useCallBack
useMemo和useCallback:用于缓存数据,性能优化;两者接收的参数是一样的,第一个参数表示一个回调函数,第二个表示依赖的数据
相同点:依赖的数据变化时,才会调用传进去的回调函数去重新计算结果,起到一个缓存的作用
不同点:
useMemo 缓存的结果是回调函数中return回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态
useCallback 缓存的结果是函数,主要用于缓存函数,应用场景如需要缓存的函数,因为函数式组件每次任何一个state发生变化,会触发整个组件更新,一些函数是没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费;另外还需要注意的是,useCallback应该和React.memo配套使用,缺了一个都可能导致性能不升反而下降。
import React,{useState,useMemo} from 'react'
function Info(props){
const [personalInfo, setPersonalInfo] = useState({
name: 'kevi kang',
gender: 'male'
})
function formatGender(gender){
return gender === 'male' ? '男' : '女'
}
const gender = useMemo(() =>{
return formatGender(personalInfo.gender)
},[personalInfo.gender])
return (
<>
<div>
姓名: {personalInfo.name} -- 性别: { gender } <br/>
<button onClick={
()=> {
setPersonalInfo({
...personalInfo,
name: 'Will Kang'
})
}
}> 点击修改名字</button>
</div>
</>
)
}
export default Info
import React, { useState, memo, useEffect, useCallback } from 'react';
type MyProps = {
addItem: () => void
children: string
}
const ListItem = memo((props: MyProps)=> {
let addItem = props.addItem
useEffect(()=>{
console.log('子组件ListItem 加载')
},[])
useEffect(()=>{
console.log('子组件render')
})
return (
<div onClick={ addItem }> { props.children } </div>
)
})
let count = 0
function List() {
let [list, setList] = useState<string[]>([])
let [name, setName] = useState('Kevin');
useEffect(()=>{
setList([
'6点起床',
'7点上班',
'8点早会'
])
}, []);
/**
* useCallback的第一个参数称为"内联回调函数",第二个参数称为"依赖项"数组。
* 返回的函数被称为memoized回调函数,该回调函数仅在某个依赖项改变时才会更新。
*
* 在子组件里面调用了useCallback返回的addI这个方法后,会执行内联回调函数;
* 然后setState,整个组件更新,addI方法也会相应的更新。
*/
const addI = useCallback(() => {
list.push('行程 '+ count++);
setList([...list])
}, [list])
const modifyName = () => {
setName('K3VIN' + (++count))
}
return (
<>
{
list.map((item, index) => {
return (
<ListItem key={index} addItem = { addI }>
{item}
</ListItem>
)
})
}
现在的名字: {name} <button onClick={modifyName}> 点击修改名字 </button>
</>
)
}
export default List
useReducer:类似于Redux中的reducer,用于管理复杂的状态逻辑。
14.react的优缺点(考)
优点:
- 提高了应用性能和开发效率
- 使用JSX,代码可读性好
- react的componentWillUnmount生命周期,能够清除相关所有事件,避免内存泄露
- 并不直接对DOM进行操作,引入了一个虚拟DOM的概念,安插在js和真实DOM中间,性能好,速度快
缺点:
每次 state
更改,render
函数都要生成完整的虚拟 DOM. 哪怕 state
改动很小,render
函数也会完整计算一遍。如果 render
函数很复杂,这个过程就白白浪费了很多计算资源
react 缺点_不玩微博的jason2的博客-CSDN博客_react缺点
15.VUE与React两个框架的区别对比 (考)
相似之处:
- 用于创建UI的js库
- 使用起来轻快便捷
- 都用了虚拟DOM
- 都是基于组件的架构
不同点 :
- vue使用的html模板;react使用的是js
- vue有双向绑定语法
- vue增加了语法糖computed和watch等,react需要自己写逻辑来实现
- react用了jsx语法
- react整体思路是编程式,推荐组件化,数据不变,单向数据流;vue数据可变,双向绑定,声明式的写法
react使用的是js
16.React的工作原理 (考)
React会创建一个虚拟的DOM。当一个组件的状态改变时,React首先会通过“diffing"算法来标记虚拟DOM中的改变,第二步是调节,会用diff的结果来更新DOM.
17.React中key作用是什么? (考)
主要用于列表中元素被修改,删除或添加的标识。在diff算法中,key用来判断该元素节点是被删除还是创建减少不必要的元素重复渲染。
18.props和state(不用看)
- state是数据结构,只能使用setState来改变
- props是组件的属性,由父组件传递给子组件,props是不可以改变的
state是局部的,除了它所在的这个组件其它组件都无法访问
- 无状态组件:没有设置state的组件(无状态组件通过函数式声明来构建,一个函数就是一个组件)
- 有状态组件:设置了state的组件(有状态组件通过component来构建,一个子类就是一个组件)
19.React可以用两种方法声明组件,他们的区别是什么,什么情况你会选择哪一种?(考)
- 函数组件:首字母大写,需要return出react元素
- 类组件:首字母大写,需要使用render方法,return出react元素
区别:
- 函数组件是无状态组件的思想
- 函数组件中不能用State,不能用组件的生命周期方法,这就决定了函数组件都是展开性组件,接收props,渲染DOM,而不关注其它逻辑
- 函数组件中没有this
- 函数组件更容易理解。当你看到一个函数组件时,你就知道它的功能只是接收属性,渲染页面,它不执行与UI无关的逻辑处理,它只是一个纯函数
// 函数组件
function Welcome(props){
return (
<h1>hello world</h1>
)
}
// 类组件:es6的加入让js直接支持使用class来定义类,react创建组件的方式是使用继承,官网推荐这种
// 使用方式,使用es6标准语法来构建
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component{
render(){
return (
<div>欢迎进入react组件</div>
)
}
}
ReactDOM.render(
<App />,document.getElementById('root')
)
20.React组件生命周期的阶段是什么?(考)
- 初始渲染阶段:组件将开始生命之旅并进入 DOM 阶段
- 更新阶段:一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才能更新和重新渲染
- 卸载阶段:组件生命周期最后阶段,组件被销毁并从 DOM 中删除
- constructor:在构造函数中初始化props和state
- componentWillMount:组件渲染之前执行,对state进行最后修改
- render:渲染
- componentDidMount: 组件渲染之后执行
componentWillReceiveProps:
这个周期函数作用于特定的 prop 改变导致的 state 转换shouldComponentUpdate:
用来做性能优化,根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。componentWillUpdate:
数据在改变之前执行componentDidUpdate:
渲染发生后立即调用componentWillUnmount:
从 DOM 卸载组件后调用。用于清理内存空间
class Timer extends React.Component {
constructor(props) {
super(props)
this.state = {seconds: 0}
}
tick(){
this.setState(state =>({
seconds: state.seconds + 1
}))
}
componentDidMount() {
this.interval = setInterval(() => this.tick(),1000)
}
componentWillMount(){
clearInterval(this.interval);
}
render() {
return (
<div> Seconds: {this.state.seconds} </div>
)
}
}
在react17 会删除以下三个生命周期
componentWillMount,componentWillReceiveProps , componentWillUpdate
21.react异步渲染(不用看)
React将setState进行异步处理,即调用setState时候不会马上更新,而是将setState的值放入一个队列,然后延时批量处理队列中的state,进行渲染。
22.调用setState之后发生了什么(考)
setState会进行状态更新,将传入的参数对象与组件当前状态合并,进行调和,经过调和过程,根据新的state,React元素会重新构建虚拟DOM,进行diff算法对比新旧虚拟DOM树的区别,进行视图更新,而不是全部渲染
setState 采用的任务队列机制,不会马上执行,而是加入队列,在下次事件循环是一次性执行
23.为什么建议传递给setState的参数是一个callback(回调函数)而不是一个对象(考)
this.props和this.state的更新可能是异步的,不能依赖他们的值去计算下一个state
24.什么是React 路由?
React 的路由是一种在单页面应用(Single-Page Application)中管理页面导航和 URL 的方式
React Router 主要包含以下几个核心组件:
-
BrowserRouter
:使用 HTML5 History API 实现的路由容器,支持使用标准的 URL 地址格式。 -
HashRouter
:使用 URL 中的哈希(#)实现的路由容器,适用于不支持 HTML5 History API 的环境。 -
Route
:用于定义路由规则和关联组件的组件,根据 URL 匹配路径来渲染相应的组件。 -
Link
:生成包含指定 URL 的链接,用于触发路由导航。 -
Switch
:用于包裹多个Route
组件,只渲染符合当前 URL 的第一个Route
。 -
Redirect
:用于重定向到指定的 URL。
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
function Home() {
return <h1>Welcome to the Home page!</h1>;
}
function About() {
return <h1>About Us</h1>;
}
function NotFound() {
return <h1>404 Not Found</h1>;
}
function App() {
return (
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route component={NotFound} />
</Switch>
</Router>
);
}
BrowserRouter
提供了路由容器,Route
定义了路由规则和对应的组件,Link
用于生成导航链接。通过 Switch
组件可以确保只渲染符合当前 URL 的第一个 Route
。
25.路由懒加载的实现
需要渲染路由页面时才加载对应的组件,而不是在应用启动时就加载所有组件。这样可以减轻应用的初始加载量,加快应用的响应速度。
下面是一个使用React.lazy()和import()方法实现路由懒加载的实例代码
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const HomePage = lazy(() => import('./HomePage'));
const AboutPage = lazy(() => import('./AboutPage'));
const ContactPage = lazy(() => import('./ContactPage'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
<Route path="/contact" component={ContactPage} />
</Switch>
</Suspense>
</Router>
);
}
在这个例子中,我们使用了 React.lazy()
和 import()
方法来实现路由懒加载。当使用 <Route>
组件加载对应的路由组件时,我们使用 lazy()
方法来异步加载组件,并使用 fallback
属性来设置组件加载过程中的 loading 界面。 在这个例子中,我们使用了 Suspense
组件来包装整个 <Switch>
组件,以便在组件加载时显示 loading 界面。
需要注意的是, React.lazy()
只支持默认导出组件,而且只能在模块的顶层使用,不能在函数内使用。同时,由于路由懒加载使用了动态导入语法,因此需要使用webpack等打包工具进行打包。
26.shouldComponentUpdate默认会有的吗
是的,每个组件都会有
27.模仿vue中v-model指令,react实现类似双向绑定
通过onChange事件监听input的value值,在通过this.setState改变显示出来
28. 区分Real DOM和Virtual DOM
Real DOM | Virtual DOM |
1.更新缓慢 | 1.更新更快 |
2.可以直接更新HTML | 2.无法直接更新HTML |
3.如果元素更新,则创建新DOM | 3.如果元素更新,则更新JSX |
4.DOM操作代价很高 | 4.DOM操作很简单 |
5.消耗的内存较多 | 5.很少消耗内存 |
29.解释 React 中 render() 的目的。
接收数据和返回展示的内容
class HelloMessage extends React.Component {
render() {
return (
<div>
Hello {this.props.name}
</div>
);
}
}
ReactDOM.render(
<HelloMessage name="Taylor" />,
document.getElementById('hello-example')
);
30.受控组件和非受控组件(考)
受控组件:通过setState的形式控制输入的值及更新
非受控组件:通过dom的形式更新值,获取值通过ref的形式去获取
// 受控
class NameForm extends React.Component{
constructor(props){
super(props)
this.state = {value: ''}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event){
this.setState({}value: event.target.value)
}
handleSubmit(event){
alert('提交的名字:' + this.state.value)
event.preventDefault()
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange}/>
</label>
<input type="submit" value="提交"/>
</form>
)
}
}
// 非受控
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Demo1 extends Component {
render() {
return (
<input />
)
}
}
ReactDOM.render(<Demo1/>, document.getElementById('content'))
在这个最简单的输入框组件里,我们并没有干涉input中的value展示,即用户输入的内容都会展示在上面。如果我们通过props给组件设置一个初始默认值,defaultValue属性是React内部实现的一个属性,目的类似于input的placeholder属性。
31.如何更新组件的状态?
- 可以用
this.setState()
更新组件的状态。 - replaceState也可以改变
class MyComponent extends React.Component {
constructor() {
super();
this.state = {
name: 'Maxx',
id: '101'
}
}
render()
{
setTimeout(()=>{this.setState({name:'Jaeha', id:'222'})},2000)
return (
<div>
<h1>Hello {this.state.name}</h1>
<h2>Your Id is {this.state.id}</h2>
</div>
);
}
}
ReactDOM.render(
<MyComponent/>, document.getElementById('content')
);
class App extends React.Component{
constructor(props);
this.state={
count:1
title:'数字计算'
}
}
handleClick=()=>{
this.replaceState({
count:this.state.count+1
})
}
render(){
return(
<button onClick={this.onClick}>点我</button>
)
}
}
count:1
32.禁止useEffect在首次渲染时执行
hook 初次渲染时不执行useEffect方法
import { useEffect, useRef, useState } from 'react';
function MyComponent() {
const [inputValue, setInputValue] = useState('');
const isInitialRender = useRef(true); // 用于标记是否是首次渲染
useEffect(() => {
// 为true,有值设置为false
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
// 模拟接口请求获取新的数据
setTimeout(() => {
const newData = 'New Data';
// 判断当前输入框是否被用户修改过
if (!inputValue) {
setInputValue(newData); // 只在输入框未被修改过时更新值
}
}, 1000);
}, [inputValue]);
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
return (
<input type="text" value={inputValue} onChange={handleInputChange} />
);
}
33.useState
函数式组件获得状态state的方式
useState返回一个数组,useState('参数'),参数即初始化的值
可以为数组,对象,字符串,数字、
import React, { useState } from 'react'
const [count, setCounnt] = useState(0);
function add() {
// 第一种写法
setCounnt(count + 1);
// 第二种
setCounnt(oldvalue => oldvalue + 1);
}
两种写法的区别
1.使用第一次写法
当进行多次setCount
时,如:
setCounnt(count + 1);
setCounnt(count + 1);
setCounnt(count + 1);
最终count
的值为1,因为setCount
会进行合并,相当于只加了一次,和setState
一样。
2.使用第二种写法时
setCounnt(oldvalue => oldvalue + 1);
setCounnt(oldvalue => oldvalue + 1);
setCounnt(oldvalue => oldvalue + 1);
最终count
的值为 3 ,因为setCount
使用函数形式,每次 oldValue 都是上一次setCount的值,进行三次,就是 0+3。
34.useEffect
函数式组件中完成类似于类组件中生命周期的功能,进行网络请求、事件监听等,引入 Effect Hook
。
可以定义多个 useEffect,第二个参数可以进行性能优化。
useEffect("回调函数",[依赖的属性])
2.1 不进行依赖限制
组件第一次加载就会执行和每次组件更新也会执行。(类似集成了componentDidMount
和componentDidUpdate
。)
useEffect(()=>{
// 代码块
})
2.2 不依赖任何 state(类似componentDidMount
,只执行一次)
useEffect(()=>{
// 组件加载完成时执行这里的代码
}, [])
2.3 组件卸载前
类似 componentWillUnmount
useEffect(()=>{
// xxxxxx代码块
return ()=>{
// 相当于componentWillUnmount,组件卸载前调用
}
}, [])
2.4 依赖特定 state(类似 componentDidUpdate)
useEffect(()=>{
// 当 count 改变,就执行代码
}, [count])
35.
useContext
在类组件中创建: const myContext = React.createContext();
<MyContext.Provider value="hello">
<Son />
</MyContext.Provider>
在函数组件中使用:
// Son 函数组件
import React, { useContext } from 'react'
const text = useContext(TContext);// text 即为 hello
36.react不更新视图怎么解决
import React, { Component } from 'react';
import { Table, Button } from 'element-react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
columns: [
{
label: "日期",
prop: "date",
width: 180
},
{
label: "姓名",
prop: "name",
width: 180
},
{
label: "地址",
prop: "address"
}
],
data: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}]
}
}
render() {
return (
<div>
<Todo list={this.state.data}/>
<Table
style={{width: '100%'}}
columns={this.state.columns}
data={this.state.data}
/>
<Button type="primary" onClick={this.aDDData.bind(this)}>添加</Button>
</div>
);
}
addData () {
let obj = {
date: '2018-05-07',
name: '小明',
address: ''
};
let data = this.state.data;
data.push(obj);
this.setState({
data: data
});
console.log(this.state);
}
}
export default App;
上面代码中 通过setState设置data的值发现视图并没有更新,原因是数组的赋值是引用传递的,data = this.state.data是执行data这个数组的内存,所以执行data.push(obj)实际上相当于执行了 this.state.data.push(obj),所以react的虚拟dom发现state里面的data没有变化,所以不更新视图,而这时可以使用一个新数组:
let data = [...this.state.data];
这样data比对以后会react会检测新旧的变化而更新dom
react 18
react18 已经不支持IE浏览器
改变根节点的挂载方式使用新的 API createRoot,使用旧的 API 仍然兼容,只有在使用 createRoot 了之后才会有 React 18 的新特性。
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>,
)
我们使用了 ReactDOM.createRoot 方法创建了一个根节点,并使用 render 方法将组件渲染到根节点中。这样可以让 React 应用更快地响应用户操作,提高用户体验。
react边界错误(16.0)
边界错误:用来捕获后代组件错误,渲染出备用页面
特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
默认情况下,若一个组件在渲染期间(render)发生错误,会导致整个组件树全部被卸载
使用方式:
import React from 'react'
import ErrorBound from "./components/common/ErrorBound"
function Comp1() {
return <div style={{
width: "90%",
height: 500,
border: "2px solid"
}}>
<h1>Comp1</h1>
<Comp2 />
</div>
}
function Comp2(props) {
return <div style={{
width: "70%",
height: "70%",
border: "2px solid"
}}>
<h1>{this.state.name}</h1>
</div>
}
function Comp3() {
return <div style={{
width: "90%",
height: 500,
border: "2px solid"
}}>
<h1>Comp3</h1>
</div>
}
export default function App() {
return <div>
<Comp1 />
<Comp3 />
</div>
}
在上面组件中,Comp2使用了未定义的state.name,导致组件错误,从而影响到整个react无法正常显示
1、使用错误边界
错误边界:是一个组件,该组件会捕获到渲染期间(render)子组件发生的错误,并有能力阻止错误继续传播
//ErrorBound
import React, { PureComponent } from 'react'
export default class ErrorBound extends PureComponent {
state = {
hasError: false
}
static getDerivedStateFromError(error) {
console.log("发生错误了");
return {
hasError: true
}
}
render() {
if (this.state.hasError) {
return <h1>出现错误了!</h1>
}
return this.props.children
}
}
import React from 'react'
import ErrorBound from "./components/common/ErrorBound"
function Comp1() {
return <div style={{
width: "90%",
height: 500,
border: "2px solid"
}}>
<h1>Comp1</h1>
<Comp2 />
</div>
}
function Comp2(props) {
return <div style={{
width: "70%",
height: "70%",
border: "2px solid"
}}>
<h1>{this.state.name}</h1>
</div>
}
function Comp3() {
return <div style={{
width: "90%",
height: 500,
border: "2px solid"
}}>
<h1>Comp3</h1>
</div>
}
export default function App() {
return <div>
<ErrorBound>
<Comp1 />
</ErrorBound>
<Comp3 />
</div>
}
此时我们创建一个组件,使用getDerivedStateFromError
静态函数,渲染子组件的过程中,一旦发生错误之后,在更新页面之前会执行该函数,函数中我们改变state的值,最后通过判断state的值动态判断是否有错误。
注意:getDerivedStateFromError
静态函数
运行时间点:渲染子组件的过程中,发生错误之后,在更新页面之前
注意:只有子组件发生错误(例中Comp2),才会运行该函数
该函数返回一个对象,React会将该对象的属性覆盖掉当前组件的state
参数:错误对象
通常,该函数用于改变状态
此外还有一个函数componentDidCatch
import React, { PureComponent } from 'react'
export default class ErrorBound extends PureComponent {
state = {
hasError: false
}
static getDerivedStateFromError(error) {
console.log("发生错误了");
return {
hasError: true
}
}
componentDidCatch(error, info) {
console.log("记录错误信息");
}
render() {
if (this.state.hasError) {
return <h1>出现错误了!</h1>
}
return this.props.children
}
}
componentDidCatch
实例方法
运行时间点:渲染子组件的过程中,发生错误,更新页面之后,由于其运行时间点比较靠后,因此不太会在该函数中改变状态
通常,该函数用于记录错误消息
细节
某些错误,错误边界组件无法捕获
自身的错误(ErrorBound自身错误)
异步的错误()
- 事件中的错误
function Comp2() {
return <div style={{
width: "70%",
height: "70%",
border: "2px solid"
}}>
<h1 onClick={()=>{
throw new Error("点击时发生的错误")
}}>Comp2</h1>
</div>
}
总结:仅处理渲染子组件期间的同步错误
react懒加载
React.lazy()函数是React官方推荐的方法,使用简单方便,适用于简单的懒加载场景。
React Loadable库提供了更多的配置选项和灵活性,适用于需要更复杂的懒加载需求。
动态import语法是一种原生的方式,无需引入额外的库,适用于简单的懒加载需求,但在不支持动态import语法的环境下无法使用。
// 1
import React, { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<div>
<h1>Lazy Loading Example</h1>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
export default App;
// 2
import React from 'react';
import Loadable from 'react-loadable';
const LoadableComponent = Loadable({
loader: () => import('./LazyComponent'),
loading: () => <div>Loading...</div>,
});
function App() {
return (
<div>
<h1>Lazy Loading Example</h1>
<LoadableComponent />
</div>
);
}
export default App;
// 3
import React, { useState, useEffect } from 'react';
function App() {
const [LazyComponent, setLazyComponent] = useState(null);
useEffect(() => {
import('./LazyComponent').then(component => {
setLazyComponent(component.default);
});
}, []);
return (
<div>
<h1>Lazy Loading Example</h1>
{LazyComponent ? <LazyComponent /> : <div>Loading...</div>}
</div>
);
}
export default App;
组件通信方式总结
方式:
props:
(1).children props
(2).render props
消息订阅-发布:
pubs-sub、event等等
集中式管理:
redux、dva等等
conText:
生产者-消费者模式
组件间的关系
父子组件:props
兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)
react 发布订阅pubs-sub、event
一种常见的方法是使用第三方库,如 EventEmitter3、mitt 或 PubSubJS,它们提供了方便的 API 来实现发布订阅模式。另外,你也可以根据需要自己实现一个简单的发布订阅模式,以下是一个基本的示例:
// eventBus.js
const eventBus = {
listeners: {},
subscribe(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
},
publish(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => {
callback(data);
});
}
}
};
export default eventBus;
在上面的例子中,ComponentA 通过 eventBus 订阅了名为 'customEvent' 的事件,并在收到事件时执行回调函数。而 ComponentB 则通过 eventBus 发布了 'customEvent' 事件,并传递了一条消息。这样就实现了两个组件之间的通信。
在上面的代码中,我们创建了一个名为 eventBus 的简单发布订阅模块。它包括了 subscribe 方法用于订阅事件和回调函数,以及 publish 方法用于发布事件和传递数据给订阅者。
然后,你可以在任何需要进行组件通信的地方引入 eventBus,并使用它来实现组件之间的通信。例如:
// ComponentA.js
import React, { useEffect } from 'react';
import eventBus from './eventBus';
function ComponentA() {
useEffect(() => {
// 订阅事件
eventBus.subscribe('customEvent', handleCustomEvent);
return () => {
// 在组件卸载时取消订阅
eventBus.unsubscribe('customEvent', handleCustomEvent);
}
}, []);
function handleCustomEvent(data) {
console.log('Received data:', data);
}
return <div>Component A</div>;
}
export default ComponentA;
// ComponentB.js
import React from 'react';
import eventBus from './eventBus';
function ComponentB() {
function handleClick() {
// 发布事件
eventBus.publish('customEvent', { message: 'Hello from Component B' });
}
return <button onClick={handleClick}>Send Message</button>;
}
export default ComponentB;
===下面不用看
34.React 中的箭头函数是什么?怎么用?
用于编写函数表达式的简短语法。这些函数允许正确绑定组件的上下文,因为在ES6中默认下不能使用自动绑定。使用高阶函数时,箭头函数非常有用
//General way
render() {
return(
<MyInput onChange = {this.handleChange.bind(this) } />
);
}
//With Arrow Function
render() {
return(
<MyInput onChange = { (e)=>this.handleOnChange(e) } />
);
}
35.区分有状态和无状态组件。
有状态组件 | 无状态组件 |
1.在内存中存储有关组件状态变化的信息 | 1.计算组件的内部状态 |
2.有权改变状态 | 2. 无权改变状态 |
3. 包含过去、现在和未来可能的状态变化情况 | 3. 不包含过去,现在和未来可能发生的状态变化情况 |
4. 接受无状态组件状态变化要求的通知,然后将 props 发送给他们。 | 4.从有状态组件接收 props 并将其视为回调函数。 |
40.React中的事件是什么?
在 React 中,事件是对鼠标悬停、鼠标单击、按键等特定操作的触发反应。处理这些事件类似于处理 DOM 元素中的事件。但是有一些语法差异,如:
- 用驼峰命名法对事件命名而不是仅使用小写字母
- 事件作为函数而不是字符串传递
事件参数重包含一组特定于事件的属性。每个事件类型都包含自己的属性和行为,只能通过其事件处理程序访问。
41.React中的合成事件是什么?
围绕浏览器原生事件充当跨浏览器对象。将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性
42.如何创建refs?
Reac.memo 缓存组件
import React from 'react'
class test extends React.Component{
state = {
myTest: 'jjj'
}
testRef = React.createRef() // 创建的是一个对象
render(){
return(
<div>
<input ref={this.testRef}/>
<button onClick={() =>{
console.log("获取输入框中的值",this.testRefs.current.value)
console.log(this.testRefs.current, '是一个对象,可以获取到DOM')
}}>点我</button>
</div>
)
}
}
export default test
43.列出一些应该使用 Refs 的情况。
通过ref来获取DOM元
- 需要管理焦点、选择文本或媒体播放时(请看React-60)
- 触发式动画
- 与第三方 DOM 库集成
//标签中设置ref,值为字符串
<input type="text" ref="name"/>
//函数中通过ref获取DOM中的值
getInputValue(){
cnosole.log(this.refs.name.value) //打印出input中输入的内容
}
class ReferenceDemo extends React.Component{
display() {
const name = this.inputDemo.value;
document.getElementById('disp').innerHTML = name;
}
render() {
return(
<div>
Name: <input type="text" ref={input => this.inputDemo = input} />
<button name="Click" onClick={this.display}>Click</button>
<h2>Hello <span id="disp"></span> !!!</h2>
</div>
);
}
}
43.如何模块化 React 中的代码?
可以使用 export 和 import 属性来模块化代码。它们有助于在不同的文件中单独编写组件。
//ChildComponent.jsx
export default class ChildComponent extends React.Component {
render() {
return(
<div>
<h1>This is a child component</h1>
</div>
);
}
}
//ParentComponent.jsx
import ChildComponent from './childcomponent.js';
class ParentComponent extends React.Component {
render() {
return(
<div>
<App />
</div>
);
}
}
44.解释react中render()的目的
每个React组件强制要求必须有一个render().它返回一个React元素,是原生DOM组件的表示。如果需要渲染多个HTML元素,则必须将它们组合在一个封闭标记内,例如<form>,<group>,<div>等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。
45.如何将两个或多个组件嵌入到一个组件中?
// 可以通过以下方式
class MyComponent extends React.Component{
render(){
return(
<div>
<h1>Hello</h1>
<Header/>
</div>
);
}
}
class Header extends React.Component{
render(){
return
<h1>Header Component</h1>
};
}
ReactDOM.render(
<MyComponent/>, document.getElementById('content')
);
46.React事件机制
<div onClick={this.handleClick.bind(this)}>点我</div>
React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件。.因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。
是怎么较少内存消耗的???
渲染组件
当react元素
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
const element = <Welcome name="Sara" />
ReactDOM.render(
element,
document.geteElementById('root')
)
47.事件处理
// 传统的
<button onclick="activateLasers()">Active Lasers</button>
// react
<button onClick={activateLaser}>
Activate Lasers
</button>
// 开关按钮
class Toggle extends React.Component{
constructor(props){
super(props)
this.state = {isToggleOn: true}
// 为了在回调中使用this,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState(state =>({
isToggleOn: !state.isToggleOn
}))
}
render() {
return (<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'
</button>)
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
)
class LoggingButton extends React.Component{
handleClick = () =>{
console.log(this)
}
render(){
<button onClick={this.handleClick}>
Click me
</button>
}
}
49.为什么在src创建子目录?
webpack只处理src的中文件。如果不把css和html放在src文件夹下,webpack发现不了。
50.react是单页面应用程序
单页面不需要加载整个页面,单页面就是一个页面,用路由来切换组件显示,页面只有一个index.html,不管什么组件都是在这个页面里的,最后都放在这里面了
非单页面:原来那种网站 每点个链接 都是整个页面刷新 有的登录都是刷新,原来网站会有很多html文件
优点:
1.良好的交互体验
单页应用的内容的改变不需要重新加载整个页面,获取数据也是通过Ajax异步获取,没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象,页面显示流畅,web应用更具响应性和更令人着迷。
2.良好的前后端工作分离模式
后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。
3.减轻服务器压力
单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
缺点:
- 首屏加载慢
解决方案:
- Vue-router懒加载:对首屏加载的速度提升得越明显。
- 使用CDN加速
简单单页面的实现
52.React-router单页面应用--路由管理;路由和组件;重定向;路由生命周期;懒加载
53.render函数中return如果没有使用()会有什么问题?
render进行return时,对于单行的内容,没必要加括号,但对于多行的内容要加括号
JSX转为js后,js会在每行自动加';'
,如果return后换行了,那么就会变成return;
55.React的事件和普通的HTML事件有什么不同? (不用考)
- 事件名称命名方式,原生事件为全小写,react 事件采用小驼峰
- 事件函数处理语法,原生事件为字符串,react 事件为函数
- react 事件不能采用 return false 的方式来阻止浏览器的默认行为,必须明确调用
preventDefault
来阻止默认行为
合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:
- 兼容所有浏览器,更好的跨平台
- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)
- 方便 react 统一管理和事务机制
事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行
56.React 组件中怎么做事件代理?它的原理是什么?(不用考)
React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。
在React底层,主要对合成事件做了:事件委派和自动绑定。
- 事件委派: React会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。
- 自动绑定: React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。
57.条件渲染
场景:返回按钮是否显示
import React,{component} from 'react'
export default class App extends Component{
state = {
list:[
{
id: 1,
isShow: false
},
{
id: 2,
isShow: true
}
]
}
render(){
return(
<div>
<ul>
{this.state.list.map(item =>{
<li key={item.id}>
{item.isShow}
{item.isShow && <button>返回</button>}
</li>
})}
</ul>
</div>
)
}
}
import React,{Component} from 'react'
class User extends Component{
render(){
return(<div>Weclome back!</div>)
}
}
class Guest extends Component{
render(){
return(<div>Weclome back!</div>)
}
}
export default class App extends Component{
state = {
isLogin: false
}
render(){
const isLogin = this.state
let button
if(isLogin){
button = <User />
}else{
button = <Guest />
}
return(<div>
{button}
</div>)
}
}
59.react无状态组件和class类组件的区别
- 函数组件是一个返回react元素的函数,体现的是无状态组件思想,函数组件中没有this,没有state,没有生命周期,决定了函数组件都是展示性组件,接收props,渲染dom
- 函数组件不需要考虑组件状态和组件生命周期方法,有很大的性能提升空间
60.React中什么是纯函数
即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
let friend = {
firstName: 'zhang',
lastName: 'san',
age: 18
}
// 非纯函数:会修改函数外变量 friend 的值
function appendMessage() {
friend.message = {
time: '2021年',
info: '学习React'
}
}
// 纯函数:返回一个新对象,且不会修改参数
function appendMessage(friend) {
let copyFriend = Object.assign({}, friend)
copyFriend.message = {
time: '2021年',
info: '学习React'
}
return copyFriend
}
62.React组件传值有哪些方式
父传子:props
子传父:通过在父组件引入的子组件中传递一个函数并传参数,子组件去触发这个函数更改参数完成数据更新
跨多层组件传值:通过context api完成
63.React中要实现一键换ui样式有哪些方案
- 准备不同主题色的样式文件
- 将用户的选择记录在本地缓存中
- 每次进入应用时,读取缓存,根据缓存的信息判断要加载哪个样式文件即可
64.路由的动态加载模块 (不用考)
webpack在打包react应用时会将整个应用打包成一个js文件,当用户首次访问时,会加载整个js文件,当应用规模越来越大时,这个js文件所包含的数据量也越来越大,网站首屏渲染的速度也就会变慢
优化方法
- 多入口:利用webpack的entry配置多入口,将应用打包分割成多个js文件。适用于多页面应用。
- 路由动态加载:利用import()方法细颗粒度的分割代码,将每一个路由到的组件从主bundle.js文件分离出来。
react的context
23.setState到底是同步还是异步
setState只在合成事件和钩子函数中是“异步”,在原生事件和setTimeout中都是同步的
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback)
中的callback
拿到更新后的结果。
setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState
,setState
的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState
多个不同的值,在更新时会对其进行合并批量更新。
===============下面不用看
12.声明式代码(编程)思想:(不用看)
就是说明下你的目的,然后具体不说明怎么去做。
举例子:去酒吧告诉服务员你要一杯鸡尾酒,服务员把做好的鸡尾酒给你,并没有说做的过程是怎样的
const toLowerCase = arr = arr.map(
value => value.toLowerCase();
)
5.还有其他的编程方式吗?(不用看)
命令式编程:就是描述代码如何工作,告诉计算机一步步的执行,先做什么后做什么。
举例子:去酒吧点一杯酒,指挥服务员
从架子上取下一个玻璃杯
把杯子放在酒桶前
打开酒桶开关,直到酒杯杯满
把做好的鸡尾酒递给顾客
const toLowerCase = arr => {
const res = [];
for (let i = 0, len = arr.length; i < len; i++) {
res.push(arr[i].toLowerCase());
}
return res;
}
6.React事件处理的几种方法(如何创建一个事件)
import React from 'react'
class Test extends React.Component{
handleClick2(){
console.log('click2')
}
hangleClick4 = () =>{
console.log('click4')
}
render(){
return(
<button onClick={ console.log('click1')}>click1</button>
<button onClick={ this.handleClick2.bind(this)}>click2</button>
<button onClick={ () => {console.log('click3')}>click3</button>
<button onClick={ this.hangleClick4 }>click3</button>
)
}
}
export default Test
8.什么是JSX呢?JSX代码到显示内容的转换过程
JSX就是JS的扩展语言(jsx语法= js语法+xml(html)语法)
是JS的语法扩展,本质上是JS对象。JSX可以很好的描述UI信息,但是浏览器无法直接读取,编译过程中会将JSX转换成JS的语法。
render(){
return(
<div>
<h1> Hello World from Edureka!!</h1>
</div>
);
}
什么是单向数据流
数据主要从父节点传到子节点(通过props),如果父级的某个props改变了,React会重新渲染所有子节点
若子组件想要改版父子组件的值应该如果去做呢?
如下:event事件,子组件是不能够在自己的组件中去修改父组件的值,所以子组件中可以通过调用父组件的方法在父组件的方法中修改父组件的值
// 父组件
import React, {Component} from 'react'
import Child from './Child'
class Parent extends Component{
// 初始化
constructor(props){
super(props)
this.state = {
message: '我是从父组件传过来的'
}
}
handleUpdate = () =>{
this.setState({
message: '子组件修改父组件的值'
})
}
// 渲染
render(){
return(
<div>
<Son msg={this.state.message} event={this.handleUpdate()}/>
</div>
)
}
}
export default Parent
// 子组件
import React, {Component} from 'react'
class Child extends Component{
// 初始化
constructor(props){
super(props)
}
// 渲染
render(){
return(
<div>
{this.props.message}
<button onClick={() =>{this.props.event()}}>子组件调用父组件的方法</button>
</div>
)
}
}
export default Child
13.向事件处理程序传递参数 (不考)
例如删除当前行的ID
<button onClick={(e) => this.delete(e,id)}>Delete Row<button>
<button onClick={this.delete.bind(this,id)}>Delete Row</button>
12.阻止React的默认行为 (不考)
e.preventDefault(),e是第三方提供的一个合成事件(注意:不能用return)
class App extends React.component{
constructor(props){
super(props);
this.state = {
}
}
hander(e){
e.preventDefault()
}
render(){
return <button onClick={ this.hander.bind(this) }>阻止事件</button>
}
}
52.什么是高阶组件
高阶组件是参数为组件,返回值为新组件的函数
React高阶组件_小小白学计算机的博客-CSDN博客_高阶组件
55.什么是渲染劫持
渲染指的是组件中的render函数return的JSX语法部分。劫持是在渲染之前进行数据等操作的处理,修改了原来组件的渲染
控制组件从另一个组件输出的能力
在高阶组件中,组合渲染和条件渲染都是渲染劫持的一种,通过反向继承,不仅可以实现以上两点,还可以增强由原组件render函数产生的React元素。
实际的操作中 通过 操作 state、props 都可以实现渲染劫持
19.什么是单向数据流和状态提升(考)
- 单向数据流:从上往下父组件将state数据流向子组件,子组件通过props取值
- 状态提升:组件之间的数据交互(很多子组件想用这个状态,将这个状态提升到最上面,通过props谁用给谁)
举例子:如果两个组件都需要用到对方的状态,那么这时候就可以用到状态提升。具体做法是把两个子组件的状态写到他们的父组件中,然后父组件把状态传递给子组件的props中去,这样子组件也相当于有状态。
父组件
import React from "react"
import Child1 from "./child1"
import Child2 from "./child2"
export default class Parent extends React.Component {
constructor() {
super()
this.state = {
money: 1
}
}
changeHander(e){
this.setState({
money: e.target.value
})
}
render() {
return (
<div>
<input type="text" value={ this.state.money } onChange={this.changeHander.bind(this)} />
<p>Parent</p>
人民比: <Child1 money={this.state.money} />
美金: <Child2 money={this.state.money} />
</div>
)
}
}
子组件1
import React from "react"
export default class Child1 extends React.Component{
constructor(){
super()
this.state = {
input1: 0
}
}
componentDidMount(){
this.setState({
input1: this.props.money
})
}
changeHander(e) {
this.setState({
input1: e.target.value
})
}
render() {
return(
<div>
{ this.props.money }
<input type="text" value={ this.state.input1 } onChange={ this.changeHander.bind(this) }/>
</div>
)
}
}
子组件2
77.如何配置React-Router
1.安装
npm install react-router-dom --save
npm install react-router-config --save
2.HashRouter与BrowserRouter区别
BrowserRouter:
原理是H5的history API,IE9及以下不兼容,需要由web server支持,在web client这边window.location.pathname被react router解析
HashRouter:
原理是URL的hash,不需要由web server支持,因为它的只有‘/’path需要由web server支持,而#/react/route URL不能被web server读取,在web client这边window,location.hash被react router解析
3.index.js
import { HashRouter as Router } from 'react-router-dom'
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);
4.app.js
// import logo from './logo.svg';
import './App.css';
import React, { Component } from 'react';
import { Link } from "react-router-dom"
import { renderRoutes } from "react-router-config"
import routes from "./routes"
class App extends Component {
render() {
return (
<div className="App" >
<Link to="/">首页</Link>
<Link to="/communication">通讯</Link>
<Link to="/find">发现</Link>
<Link to="/about">我的</Link>
<div>
{renderRoutes(routes)}
</div>
</div>
);
}
}
export default App;
5.在创建一个routes文件夹 index.js
import Home from "../components/home"
import Communication from "../components/communication"
import Find from "../components/find"
import About from "../components/about"
const routes = [
{
path: "/",
exact: true,
component: Home
},
{
path: "/communication",
component: Communication,
// routes: [
// {
// path: '/communication/:id',
// component: Course
// }
// ]
},
{
path: "/find",
exact: true,
component: Find
},
{
path: "/about",
exact: true,
component: About
},
]
export default routes
6.在建components 文件夹 里面对应的页面
React-Router 路由配置_初夏七鹿的博客-CSDN博客_react-router 路由配置
高阶组件用法及作用
react中高阶组件用了什么设计模式
使用了装饰模式,高阶组件的运用:
function withWindowWidth(BaseComponent) {
class DerivedClass extends React.Component {
state = {
windowWidth: window.innerWidth,
}
onResize = () => {
this.setState({
windowWidth: window.innerWidth,
})
}
componentDidMount() {
window.addEventListener('resize', this.onResize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResize);
}
render() {
return <BaseComponent {...this.props} {...this.state}/>
}
}
return DerivedClass;
}
const MyComponent = (props) => {
return <div>Window width is: {props.windowWidth}</div>
};
export default withWindowWidth(MyComponent);
复制代码
装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下:
@testable
class MyTestableClass {
}
react hooks中的 userLayouteffect 和 userEffect 有啥区别
useEffect 是异步执行的,而 useLayoutEffect 是同步执行的。
useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。
react hooks 为什么不能在循环和条件判断语句中调用
这是因为React通过单链表来管理Hooks。update阶段,每次调用 useState,链表就会执行 next 向后移动一步。如果将 useState 写在条件判断中,假设条件判断不成立,没有执行里面的 useState 方法,会导致接下来所有的 useState 的取值出现偏移,从而导致异常发生。
30.什么是 Props?
只读组件,必须保持纯函数,即不可变。它们总是再整个应用中从父组件传递到子组件。子组件永远不能将prop送回到父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据
31.React中的状态是什么?它是如何使用的?
是React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与props不同,它们是可变的,并创建动态和交互式组件。可以通过this.state()访问它们。
react hook中userCallback的用途?
优化子组件渲染次数
react hooks 中的 useCallback 和 useMemo
useMemo和useCallback接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据
共同作用:依赖数据发生变化, 才会调用,也就是起到缓存的作用。useCallback缓存函数,useMemo缓存返回值。
react hooks 函数组件实现 shouldcompensUpdate?
使用 userMemo 实现。
父组件调用子组件,父组件中的任何一个state发生变化,子组件中的方法代码都会重新进行渲染使用 userMemo 可解决重复渲染和多余渲染问题
react 中的 hooks 解决了什么问题?为啥要出现 hooks ?
① 从组件中提取状态逻辑,解决了在组件之间复用状态逻辑很难的问题;
② 将组件中相互关联的部分拆分成更小的函数,解决了复杂组件的问题;
③ 在非class的情况下使用更多的React特性,解决了class组件与函数组件有差异的问题。
82.hooks父子传值
父传子
在父组件中用useState声明数据
const [ data, setData ] = useState(false)
把数据传递给子组件
<Child data={data} />
子组件接收
export default function (props) {
const { data } = props
console.log(data)
}
子传父
子传父可以通过事件方法传值,和父传子有点类似。
在父组件中用useState声明数据
const [ data, setData ] = useState(false)
把更新数据的函数传递给子组件
<Child setData={setData} />
子组件中触发函数更新数据,就会直接传递给父组件
export default function (props) {
const { setData } = props
setData(true)
}
如果存在多个层级的数据传递,也可依照此方法依次传递
// 多层级用useContext
const User = () => {
// 直接获取,不用回调
const { user, setUser } = useContext(UserContext);
return <Avatar user={user} setUser={setUser} />;
};
使用过的Redux中间件
83.介绍redux,主要解决什么问题
redux是为了解决react组件间通信和组件间状态共享而提出的一种解决方案,主要包括3个部分,(store + action + reducer)。
store:用来存储当前react状态机(state)的对象。connect后,store的改变就会驱动react的生命周期循环,从而驱动页面状态的改变
action: 用于接受state的改变命令,是改变state的唯一途径和入口。一般使用时在当前组件里面调用相关的action方法,通常把和后端的通信(ajax)函数放在这里
reducer: action的处理器,用于修改store中state的值,返回一个新的state值
主要解决什么问题:
1、组件间通信
由于connect后,各connect组件是共享store的,所以各组件可以通过store来进行数据通信,当然这里必须遵守redux的一些规范,比如遵守 view -> aciton -> reducer的改变state的路径
2、通过对象驱动组件进入生命周期
对于一个react组件来说,只能对自己的state改变驱动自己的生命周期,或者通过外部传入的props进行驱动。通过redux,可以通过store中改变的state,来驱动组件进行update
3、方便进行数据管理和切片
redux通过对store的管理和控制,可以很方便的实现页面状态的管理和切片。通过切片的操作,可以轻松的实现redo之类的操作
在React中页面重新加载时怎样保留数据?
这个问题就设计到了数据持久化, 主要的实现方式有以下几种:
Redux: 将页面的数据存储在redux中,在重新加载页面时,获取Redux中的数据;
data.js: 使用webpack构建的项目,可以建一个文件,data.js,将数据保存data.js中,跳转页面后获取;
sessionStorge: 在进入选择地址页面之前,componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据
history API: History API 的 pushState 函数可以给历史记录关联一个任意的可序列化 state,所以可以在路由 push 的时候将当前页面的一些信息存到 state 中,下次返回到这个页面的时候就能从 state 里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。
在React中怎么使用sync/await?
async/await是ES7标准中的新特性。如果是使用React官方的脚手架创建的项目,就可以直接使用。如果是在自己搭建的webpack配置的项目中使用,可能会遇到 regeneratorRuntime is not defined 的异常错误。那么我们就需要引入babel,并在babel中配置使用async/await。可以利用babel的 transform-async-to-module-method 插件来转换其成为浏览器支持的语法,虽然没有性能的提升,但对于代码编写体验要更好。
84.React层面的性能优化
对于类组件和函数式组件来看,都可以从以下几个方面去思考能够进行性能优化
- 减少重新render的次数
举例子:
React更新流程如下,我们可以有两种优化角度
1.props/state变化 --->render函数变化这个阶段,减少render的执行次数
2.新旧DOM树进行diff--->计算出差异进行更新,减少差异内容
类组件:
(1)shouldComponentUpdate
当props和state更新时,render函数就会执行,
此时我们可以通过生命周期 shouldComponentUpdate来控制是否需要render重新执行,当不写这个生命周期时,React内部决定render是否变化的函数就会返回true,即默认每次都更新。
state = { count: 0 }
shouldComponentUpdate(nextProps, nextState){
if(nextProps.count === this.state.count){
return false
}
return true
}
- 减少渲染的节点
(2)减少差异
虚拟DOM的diff算法中为了提高性能,只对每一层进行比较,不会跨层比较,有key的时候会比对相同的key,没有key时内容不同就直接替换,当我们进行逆序增加数据时,没有唯一的key,会导致相同的数据每次都会重新渲染 。每个元素的唯一id,因为索引值会根据遍历的数据变化。
<ul>
<li key="1">千与千寻的神隐</li>
<li key="2">龙猫</li>
<li key="3">侧耳倾听</li>
<ul>
<ul>
<li key="1">起风了</li>
<li key="2">千与千寻的神隐</li>
<li key="3">龙猫</li>
<li key="4">侧耳倾听</li>
<ul>
- 降低渲染计算量
- 合理设计组件
高耦合低内聚(减少无必要的重复渲染;减小重复渲染时影响得范围)
在react里时间耗时最多的一个地方是reconciliation,如果不执行render,也就不需要 reconciliation,所以可以看出减少render在性能优化过程中的重要程度了
32.区分状态和 props
条件 | State | Props |
1.从父组件中接收初始值 | Y | Y |
2.父组件可以改变值 | N | Y |
3.在组件中设置默认值 | Y | Y |
4.在组件的内部变化 | Y | N |
5.设置子组件的初始值 | Y | Y |
6.在子组件的内部更改 | N | Y |
85.react-router里的
<Link>
标签和<a>
标签有什么区别
Link组件最终会渲染为HTML标签<a>,它的to,query,hash属性会被组合在一起并渲染为href属性。虽然Link被渲染为超链接,但在内部实现上使用脚本拦截了浏览器的默认行为,然后调用了history.pushState方法
Link 只负责触发 url 变更,Route 只负责根据 url 渲染组件
相比于 <a>
标签,<Link>
避免了不必要的渲染
react 项目中使用 class 组件和 函数组件要怎么通信?
用 ref 通信
具体实例参考:react 类组件(父组件)与函数组件(子组件) ref 通信_yang423712的博客-CSDN博客_react父子组件通信ref
如何在React中使用innerHTML
增加dangerouslySetInnerHTML属性,并且传入对象的属性名叫_html
function Component(props){
return <div dangerouslySetInnerHTML={{_html:'<span>你好</span>'}}>
</div>
}
22.关于this绑定==组件绑定点击事件
// bind
// 1
<button onClick={this.handleClick.bind(this)}>hello<button/>
// 2
clicked(param,event){
console.log(param) //hello world
console.log(event.target.value) //按钮
}
render(){
return (
<React.Fragment>
<button value="按钮" onClick={this.clicked.bind(this,"hello world")}>点击</button>
</React.Fragment>
)
}
// 2.在构造函数中默认绑定this(推荐)
this.handleClick = this.handleClick.bind(this)
// 3.使用箭头函数来处理,handleClick点击事件的方法
<button onClick={(e) => this.handleClick(e)}>Click me</button>
23.setState第二个参数的作用
该函数会在setState函数调用完成并且组件开始重渲染的时候被调用,我们可以用该函数来监听渲染是否完成
redux请求中间件如何处理并发
使用redux-Saga redux-saga是一个管理redux应用异步操作的中间件,用于代替 redux-thunk 的。它通过创建 Sagas 将所有异步操作逻辑存放在一个地方进行集中处理,以此将react中的同步操作与异步操作区分开来,以便于后期的管理与维护。 redux-saga如何处理并发:
React组件中怎么做事件代理
React组件事件代理的原理
深入理解React:事件机制原理 - OneForCheng - 博客园
React怎么做数据的检查和变化
- props组件属性,专门用来连接父子组件间通信,父组件传输父类成员,子组件可以利用但不能编辑父类成员
- state:专门负责保存和改变组价内部的状态
数据传递:在React中,父组件给子组件传递数据时,通过子组价设置props的方式,子组件取得props中的值,即可完成数据传递,被传递数据的格式可以是任何js可识别的数据结构props一般只作为父组件给子组件传递数据用,不要试图去修改自己的props
数据改变:props不能被自身修改,如果组件内部的属性发生变化使用state
this.setState({})
React会实时监听每个组件的props和state的值,一旦有变化,会立刻更新组件,将结果重新渲染到页面上,state,props
react-router怎么实现路由切换
使用
import
时,webpack
对node_modules
里的依赖会做什么
React
中Dom
结构发生变化后内部经历了哪些变化
React 代码层的优化可以说一下么?(不用看)
大概说了 class
组件和 function
组件两种情况,
核心是通过减少渲染次数达到优化目的
React.Fragment来避免创建不必要的元素
React.Memo中父组件每次更新都会导致子组件重新渲染,即使子组件的状态没有发生变化(为了减少重复渲染,我们可以使用 React.memo
来缓存组件,这样只有在传入组件的状态值发生变化时才会从新渲染。如果传入的值相同,则会返回缓存的组件)
React
挂载的时候有3个组件,textComponent、composeComponent、domComponent,区别和关系,Dom结构发生变化时怎么区分data的变化,怎么更新,更新怎么调度,如果更新的时候还有其他任务存在怎么处理
Redux中异步的请求怎么处理
redux的设计思想
接入redux的过程
绑定connect的过程
Redux在状态管理方面解决了React本身不能解决的问题
遇到性能问题一般在哪个生命周期里解决
写react有哪些细节可以优化
React的事件机制(绑定一个事件到一个组件上)
React子父组件之间如何传值
介绍下React高阶组件,和普通组件有什么区别
页面上有1万个button如何绑定事件
循环绑定时的index是多少,为什么,怎么解决
页面上有一个input,还有一个p标签,改变input后p标签就跟着变化,如何处理
监听input的哪个事件,在什么时候触发
Redux怎么实现属性传递,介绍下原理
React-Router版本号
Redux状态管理器和变量挂载到window中有什么区别
React中
setState
后发生了什么
seetState()将总会触发一次重绘,除非在shouldComponentUpdate()中实现了条件渲染逻辑。如果可变对象被使用了,但又不能在shouldComponentUpdate() 中实现这种逻辑,仅在新state和之前的state存在差异的时候调用setState()可以避免不必要的重新渲染。通常state中只来管理和渲染有关的状态,从而保证setState改变状态和渲染有关的状态。这样子就可以避免不必要的重复渲染。其他和渲染无关的状态,可以直接以属性的形式保存在组件中,在需要的时候调用和改变,不会造成渲染。同时需要避免不必要的修改,当 state 的值没有发生改变的时候,尽量不要使用 setState 。虽然 shouldComponentUpdate 和PureComponent 可以避免不必要的重复渲染,但是还是增加了一层 shallowEqual 的调用,造成多余的浪费。
setState
为什么默认是异步;setState
什么时候是同步的
react设计思路
react常见的通信方式
- 父子组件通信:父组件可以向子组件通过传 props 的方式,向子组件进行通讯
- 跨级组件通信:
Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context
通信再适合不过 - 非嵌套组件通信
父子通信
- 父传子 --> props
- 子传父--->props+回调函数
跨级组件通信
- props逐层传递(不考虑)
- context传递
非嵌套组件通信(如兄弟组件等)
使用机制:消息订阅与发布
react组件通信的几种方式_卡布达。的博客-CSDN博客_react组件通信
redux整体的工作流程
首先,我们看下几个核心理念:
Store:保存数据的地方,
- 你可以把它看成一个容器,整个应用只能有一个Store。
- State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。
- Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
- Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。
- Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。
- dispatch:是View发出Action的唯一方法。
然后我们过下整个工作流程:
- 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。
- 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
- State一旦有变化,Store就会调用监听函数,来更新View。
React数据流
哪些方法会触发React重新渲染?
setState()方法被调用
父组件重新渲染
重新渲染render会做些什么?
会对新旧VNode进行对比,也就是我们所说的Diff算法
对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,没遍历到一个节点,就把该节点和新的节点树进行对比,如果有差异就到放到一个对象里面
说说React有什么坑点
JSX做表达式判断的时候需要转为boolean类型
如果不使用!!b进行强转数据类型会在页面里面输出0
render() {
const b = 0;
return <div>
{
!!b && <div>这是一段文本</div>
}
</div>
}
react的理念是什么(拿函数式编程来做页面渲染)
为什么使用Hooks?
使用hooks理由
- 高阶组件为了复用,导致代码层级复杂
- 生命周期复杂
- 写成functional组件,无状态组件,因为需要状态,又改成了class,成本高
useState(保存组件状态)
const [state,setState] = useState(initialState)
常用hooks有哪些?
useState:保存组件内部状态的函数,提供了setState改变组件状态(初始化的时候只执行一次)
useEffect:在函数组件中执行副作用操作,componentDidMount,componentDidUpdate和componentWillUnmount(内部不能修改state)
负责对某些值进行实时监控。如果第二个可选参数为空,表明在第一次渲染时进行。
如果是需要清除的副作用,一个return一键清除。【数据请求】
useContext:跨组件传值
useReducer: useState的加强版,在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
useCallback:和useEffect类似,把函数做为第一个参数,把函数的依赖项作为第二个参数传入,改回调函数只有在函数的依赖项发生改变时才会调用,返回的是一个回调函数。
useMemo和uesCallback类似... ...返回的是一个值。
useRef const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。
你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。
然而,useRef() 比 ref 属性更有用。它可以<a href="https://知乎 - 有问题,就会有答案">很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。
这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
useEffect的回调函数为什么无法使用async?
react本身并不支持这么做,理由effect function 应该返回一个销毁函数(effect:是指return返回的cleanup函数),如果useEffect第一个参数传入async,返回值则变成了Promise,会导致react在调用销毁函数的时候报错:function.apply is undefined
useCallback和useMemo的区别?他们是如何进行缓存的?
useCallback返回的是函数,useMemo 返回的是值。
useCallback在什么情况下不适用?
使用 useCallback 避免频繁调用,但当一个 useCallback 的依赖项变化后,这个 useEffect 会被执行。
· 记忆效果差,依赖变化时,函数也被重新生成
· 想要记忆效果好(也就是依赖为[])又拿不到最新的state
· useCallback返回的函数也是具有闭包效应,即我们在其他hooks中使用的时候useCallback返回的函数也是那次闭包环境下产生的函数。
useState是同步还是异步?如果是异步,如何变为同步?
什么场景下是异步的?
只要在react的管控下的setState都是异步的:
一是提高效率,每次修改state的值都会造成render的重新渲染,将多次修改state的值合并统一更新可以提高性能;
二是render的更新会晚一些,如果render中有子组件,子组件的props依赖于父组件的state,props和state就不能保持一致
如何获取异步时的state的值
1.在setState的回调函数中
this.setState({
count: this.state.count + 1}},
()=>{ console.log(this.state.count)})
2.在componentDidUpdate中获取
componentDidUpdate(){
console.log(this.state.count)}
是同步的
通过 this.setState 修改状态之后,在它的下一行输出 state 是新的值
什么场景下同步的?
Reduce 方法里的 setState 是同步更新的。
setTimeout帮助了setState逃脱了react的管控,所以是同步的。
① 原生js获取dom元素,并绑定事件
<button id="addBtn">点我+1</button>componentDidMount(){
const addBtn = document.getElementById('addBtn')
changeBtn.addEventListener('click',()=>{
this.setState({ count: this.state.count + 1})
console.log(this.state.message)
})}
② 计时器 setTimeout
<button onClick={ e => this.addOne() }>点我+1</button>addOne(){setTimeout(()=>{ this.setState({ count: this.state.count + 1 })
console.log(this.state.count ) },0)}
想同步获取最新的值的代码写入到回调函数中,通过这种方式进行处理;
hook原理
如何在React中使用innerHTML
增加dangerouslySetInnerHTML属性,并且传入对象的属性名叫_html
function Component(props){
return <div dnagerouslySetInnerHTML={{_html:'<span>你好</span>'}}></div>
}
66.说说你对react的渲染原理的理解
67.说说Context有哪些属性
68.怎么使用Context开发组件
69.React中在哪捕获错误?
[react] React中在哪捕获错误?_「违规用户」的博客-CSDN博客_react 捕获错误
70.react组件的构造函数有什么用
提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的方法
React 高阶组件、Render props、hooks 有什么区别,为什么要不断迭代
高频react面试题20道(附详解)_程序员成长指北-CSDN博客
虚拟DOM原理剖析
react动画特效
react组件化思想