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
原因:批处理机制
什么时候开启批处理?
hooks相关
useRef createRef的区别是啥
从创建组件到挂载到DOM阶段。初始化props以及state, 根据state与props来构建DOM 组件依赖的props以及state状态发生变更,触发更新 销毁阶段 在第1阶段,使用createRef与useRef两者是没有区别。但是在第2阶段, 也就是更新阶段两者是有区别的。我们 知道,在一个局部函数中,函数每一次执行,都会在把函数的变量重新生成一次。createRef 每次渲染都会返回一个新的引用,而useRef 每次都会返回相同的引用。 实际应用的区别useRef 仅能用在FunctionComponent,createRef 仅能用在ClassComponent
react-dom的render跟createPortal的区别是什么
`createPortal`的官方描述:
Portal 将提供一种将子节点渲染到 DOM 节点中的方式,**该节点存在于 DOM 组件的层次结构之外**。
1.“该节点存在于 DOM 组件的层次结构之外”是什么意思?
这个很好理解,通常我们是把组件都渲染在页面上一个
id为root的div容器中。这里所说的“之外”的意思,就是这个组件不渲染在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降低其计算的优先级,使得避免整个应用变得卡顿。
跟防抖还是有比较大的区别的,那么不论机器快慢,网络情况如何,始终会在用户停止输入后的固定之间才执行。
使用场景:输入框搜索
实现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,显然是不合理的,在函数组件中没有这个问题。
原因:虽然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模块内部的其他变量,闭包就会产生。

**当**useState**在**组件**中执行时,访问了**state**中的变量对象,那么闭包就会产生**。根据闭包的特性,state模块中的state变量,会持久存在。因此当Demo函数再次执行时,我们也能获取到上一次Demo函数执行结束时state的值。 这就是React Hooks能够让函数组件拥有内部状态的基本原理。
### 闭包陷阱
见PPT最后