react 100问

263 阅读13分钟

react 和 react-dom 的区别是什么?

ReactDom只做和浏览器或DOM相关的操作,例如ReactDOM.render()和ReactDOM.findDOMNode()。如果是服务器端渲染,可以ReactDOM.renderToString()。除这些以外的其他所有的事情都是react做的。之所以要分成两个包?因为React不仅能用在Web页面,还能用在服务器端SSR,移动端和桌面端,而ReactDOM只负责和Web页面的DOM打交道

redux 和 mobx 的区别和优劣?用过 redux-saga 吗?

setState什么时候是同步的?什么时候是异步的?这里的异步指的是?

React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state 。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

原因:  在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state 合成事件是异步 异步指的是多个state会合成到一起进行批量更新在React 18 之前,我们只在 React 事件处理函数 中进行批处理更新。默认情况下,在promise、setTimeout、原生事件处理函数中、或任何其它事件内的更新都不会进行批处理,在 18 之后,任何情况都会自动执行批处理,多次更新始终合并为一次 批处理机制:setstate不会立即更新state,而是将更新数据放入队列,在事件循环结束统一更新,提高效率

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return null;
  }
};

在react 18并发模式下: 0 0 1 1 其他同步模式下: 0 0 2 3 原因:批处理机制 什么时候开启批处理? image

hooks相关

useRef createRef的区别是啥

从创建组件到挂载到DOM阶段。初始化props以及state, 根据state与props来构建DOM 组件依赖的props以及state状态发生变更,触发更新 销毁阶段 在第1阶段,使用createRef与useRef两者是没有区别。但是在第2阶段, 也就是更新阶段两者是有区别的。我们 知道,在一个局部函数中,函数每一次执行,都会在把函数的变量重新生成一次。createRef 每次渲染都会返回一个新的引用,而useRef 每次都会返回相同的引用。 实际应用的区别useRef 仅能用在FunctionComponent,createRef 仅能用在ClassComponent

react-domrendercreatePortal的区别是什么

`createPortal`的官方描述:
Portal 将提供一种将子节点渲染到 DOM 节点中的方式,**该节点存在于 DOM 组件的层次结构之外**。

1.“该节点存在于 DOM 组件的层次结构之外”是什么意思?

这个很好理解,通常我们是把组件都渲染在页面上一个idrootdiv容器中。这里所说的“之外”的意思,就是这个组件不渲染在root中,在一个新的容器中,也可以直接放到body最后。

难道使用 render 不能把子节点渲染到父组件以外的 DOM 节点吗?

!!!之前一直以为 render 只能使用一次,或者不能在额外的节点重新进行渲染,结果都不是

代码示例

总结

意义在捕获事件,事件冒泡就不详细解释了。在使用 createPortal 渲染时,比如组件 A,虽然它渲染在一个新的元素中,但调用 A 的组件 B 是可以捕获 A 中的事件。如果使用 render 就无法捕获。

比如 B 的根元素上有一个 click 事件,如果使用 render 渲染 A,点击 A 的区域,B 的 click 事件是不会触发的;但如果使用 createPortal,则会触发。 2.为什么直接调用createPortal不能使得页面更新,而是一定要将创建的Portal放入到组件树? 因为它相当于返回了一个createElement并且给其传递了一个parent的参数,只是生成一个vnode并不渲染他。

  • ref的回调函数在didmount执行之后的情况有哪些?为什么? 通常来说他的回掉函数都是在挂载钩子之后执行的,这样的话就能保证在挂载的钩子里获取到ref,以下的情况将无法保证:根本原因是:Refs don’t get set for elements that weren’t rendered 场景一:
componentDidMount() {
  // can use any refs here
}

componentDidUpdate() {
  // can use any refs here
}

render() {
  // as long as those refs were rendered!
  return <div ref={/* ... */} />;
}

场景二:

<MyPanel>
  <div ref={this.setRef} />
</MyPanel>
// 如果子组件没有渲染他的children的话也不会执行

场景三 另一种情况是嵌套使用了ReactDom.render

componentDidUpdate() {
  ReactDOM.render(this.props.children, this.targetEl);
}

render() {
  return null;
}

setState同一个值会rerender吗

函数组件不会 类组件会

useState的初始化函数 rerender的时候会执行吗?

不会,就像类的构造函数,索然rerender的时候整个函数都重新执行了,但是state的初始值不会变,如果传入的是一个函数,就算函数的返回值变了也不会变

const App = () => {

const [a,setA]=useState(Math.random())

const [_,setB]=useState('')

const hanldeChange=e=>{

setB(e.target.value)

}

return <><Input placeholder="Basic usage" value={_} onInput={hanldeChange}/><span>{a}</span></>;

}

同样的useRef也不会

const a=useRef(Math.random())

将一直保持原样

useDeferredValue是什么?有啥用

如果说某些渲染比较消耗性能,比如存在实时计算和反馈,我们可以使用这个Hook降低其计算的优先级,使得避免整个应用变得卡顿。 跟防抖还是有比较大的区别的,那么不论机器快慢,网络情况如何,始终会在用户停止输入后的固定之间才执行。 使用场景:输入框搜索

image.png

实现componentdidupdate

 useEffect(() => {
    // 在每次组件更新后执行
    if (prevPropsRef.current) {
      // 进行你要执行的操作,相当于 componentDidUpdate 方法中的逻辑
      console.log('Component updated');
    }

    // 更新 prevPropsRef 的值
    prevPropsRef.current = props;
  });
                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                    

原文链接:blog.csdn.net/weixin_5337…

useID是什么

为什么redux也是用的context 能做到按需渲染?

因为它有收集依赖,context的value变化的时候会通知所有的订阅者,订阅者拿到变化的值

为什么useEffect的callback不能使用async?

因为async函数返回的是一个Promise,而useEffect期望他要么什么都不返回要么就返回一个cleanup函数。 卸载的时候只会判断一下 destroy 不是 undefined 就执行,所以如果 useEffect 第一个参数传入 async, 那么这里的 destroy 就是一个 promise 对象,对象是不能执行的,所以报错。

useState 为什么不能放到条件语句里面?

  • React 依赖于Hooks的调用顺序来正确地关联组件内部的状态和副作用。当Hooks在每次渲染时按照相同的顺序被调用,React 能够准确地维护state以及其他Hooks相关的数据结构。如果把Hooks放在条件语句中,就可能会导致Hooks的调用顺序不一致。
    • React 通过一个内部的链表结构来跟踪每个 Hook 的状态。
    • 当组件首次渲染时,每个 Hook 按照出现的顺序被添加到这个链表中,并且它们的状态被初始化。在后续的渲染中,React 会按照相同的顺序遍历这个链表来获取和更新 Hook 的状态。
  • Hooks利用了 JavaScript 的闭包来保存和更新状态。当Hooks被放置在条件语句中时,可能会导致闭包捕获到错误的状态值
import React, { useState } from 'react'; const MyComponent = () => { let showCounter = true; if (showCounter) { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); } return <div>No counter to show.</div>; }; export default MyComponent;

useState再次被调用时,它会被当作一个新的 Hook,但是之前的increment函数(闭包)所捕获的count状态是旧的引用。当点击increment按钮时,这个闭包可能不会按照预期更新状态,导致错误的行为

函数组件跟类组件的区别?React为什么放弃类组件

React 中类组件和函数组件的差异,就表象来说可以说很多条

  • 类组件有生命周期,函数组件没有
  • 类组件需要继承 Class,函数组件不需要 。。。 相比之下类组件大而全,学习成本也就更高。函数组件相比较类组件,优点是更轻量与灵活,便于逻辑的拆分复用。但这并不是核心原因。核心原因是:函数式组件捕获了渲染时所使用的值,这是两类组件最大的不同 React 框架有一个经典的公式是 UI = f(data),React框架做的本质工作就是吃入数据,吐出UI。也就是说React的数据应该紧紧的和渲染绑定在一起,但是问题的关键就在于类组件是做不到这一点的。
类组件
class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };
  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };
  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}
// 函数组件
function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };
  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };
  return (
    <button onClick={handleClick}>Follow</button>
  );
}

如下:我先选择用户A,点击关注按钮,然后延时3秒弹出关注成功的弹窗,然后迅速切换到用户B,这个时候弹出的关注成功显示的是关注成功B,显然是不合理的,在函数组件中没有这个问题。

image.png 原因:虽然props不可改变,但是this里面的东西是可变的,this.props 的每次调用都会去获取最新的 this 值,这也是React保证数据实时性的重要手段。 函数组件能做到是因为:props 会在函数执行的瞬间就被捕获,而 props 本身又是不可变值,所以我们可以确保从当前开始读取到的 props 都是最初捕获到的。当父组件传入新的 props 尝试重新渲染函数时,本质是基于新的 props 入参重新调用了一次函数,并不会影响上一次调用。这就是 Dan 所说的函数式组件捕获了渲染所使用的值,并且我们还能进一步意识到:函数组件真正将数据和渲染紧紧的绑定到一起了

很多人认为在函数组件中延迟输出的 state 是调用时的 state,而不是最新的 state 是一个Bug,恰恰相反,这是一个函数式组件的特性,是真正践行了React设计理念的正确方式

显而易见的是,函数组件更符合 React 团队的设计理念,并且代码易于拆分和复用,用脚投票都知道 React 团队为什么要推出 Hooks 来扩展函数组件的功能,并且倡导大家使用函数组件了。

什么是HOC?以及它的优缺点?

hooks出现之前react的复用方式

HOC

高阶组件可以看作React对装饰模式的一种实现,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。

function visible(WrappedComponent) {
  return class extends Component {
    render() {
      const { visible, ...props } = this.props;
      if (visible === false) return null;
      return <WrappedComponent {...props} />;
    }
  }
}

实现方式

属性代理
function proxyHOC(WrappedComponent) {
  return class extends Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
}
```t
对比原生组件增强的项:

-   可操作所有传入的`props`
-   可操作组件的生命周期
-   可操作组件的`static`方法
-   获取`refs`

### 反向继承

返回一个组件,继承原组件,在`render`中调用原组件的`render`。由于继承了原组件,能通过this访问到原组件的`生命周期、props、state、render`等,相比属性代理它能操作更多的属性。

function inheritHOC(WrappedComponent) { return class extends WrappedComponent { render() { return super.render(); } } } 复制代码


对比原生组件增强的项:

-   可操作所有传入的`props`
-   可操作组件的生命周期
-   可操作组件的`static`方法
-   获取`refs`
-   可操作`state`
-   可以渲染劫持

### HOC的缺陷
-   `HOC`需要在原组件上进行包裹或者嵌套,如果大量使用`HOC`,将会产生非常多的嵌套,这让调试变得非常困难。
-   `HOC`可以劫持`props`,在不遵守约定的情况下也可能造成冲突(需要透传不相关的props)。
-   只支持类组件
---
### hooks为什么不能再if用
React初始化阶段会构建一个hook链表,更新阶段会根据useState的执行顺序去遍历链表取值,如果前后执行顺序不一致,就会导致取出的值不对应

### useRef vs outside variable
useRef保存的对象在组件卸载的时候会自动释放,后者不会,容易造成内存浪费

### useCallback原理

useCallback 在 memorizedState(hook链表 上用于存放hook当前值的) 上放了一个数组,第一个元素是传入的回调函数,第二个是传入的 deps(对 deps 做了下 undefined 的处理)。更新的时候把之前的那个 memorizedState 取出来,和新传入的 deps 做下对比,如果没变,那就返回之前的回调函数,也就是 prevState[0]。

### hooks依赖的比较
就是个浅比较,所以如果是依赖中放的是个对象,对象的引用不变的话也是不行的

### useLayoutEffect vs useEffect
react 内存在高优先级插队机制,对 useEffuseLayoutEffect 的 create 是在 layout 阶段被同步处理的调度可能被打断,因此useEffect是一个异步操作不会阻塞dom更新,其上一轮的 destroy 是在 mutation 阶段被同步处理的,因此在该 hook 内调用耗时操作会阻塞页面更新

`useLayoutEffect` 其实是 `useEffect` 的一种版本,传入的参数也一样,只是**执行时机不一样**,它会在浏览器**重绘 (repaints) 前**执行。

#### commit的四个部分
-   Commit的开始阶段:先执行一下之前未执行到的useEffect(这跟useEffect的异步调用特点有关系,如有更高优先级的任务进入到commit阶段,上一次任务的useEffect还没得到执行)
-   before mutation阶段(执行 DOM 操作前):读取组件变更前的状态,针对函数组件,将useEffect放到调度任务当中,并不会真正的执行它
-   mutation阶段(执行 DOM 操作):针对函数组件,执行useLayoutEffect的销毁函数。
-   layout阶段 (此阶段开始之前真实DOM已渲染完成):在DOM操作完成后,读取组件的状态,针对函数组件填充useEffect 的 effect执行数组,并调度useEffect,同步执行useLayoutEffect

`useLayoutEffect` 可能会造成性能的问题,因为在 `useLayoutEffect` 里的代码会阻碍浏览器重绘 (repaints) ,太频繁使用可能会造成整个应用程式缓慢

### hooks持久化状态的原理
只要该函数在外部执行访问了该JS模块内部的其他变量,闭包就会产生。
![img](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/d2ffbb4519d047e596d1628b1822695a~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5L2g5Y-I5YeJ5LqG:q75.awebp?rk3s=f64ab15b&x-expires=1770960346&x-signature=BMVsHZ6Wu2yIct8jMYpIUv7r4Ls%3D)
   **当**useState**在**组件**中执行时,访问了**state**中的变量对象,那么闭包就会产生**。根据闭包的特性,state模块中的state变量,会持久存在。因此当Demo函数再次执行时,我们也能获取到上一次Demo函数执行结束时state的值。 这就是React Hooks能够让函数组件拥有内部状态的基本原理。
   
### 闭包陷阱
见PPT最后