0. React hooks面试题
- juejin.cn/post/707447… 面试官:我看看你的React Hooks掌握的怎么样?
- juejin.cn/post/707447… React-Hooks 面试解答
- cloud.tencent.com/developer/a… React-hooks面试考察知识点汇总
- segmentfault.com/a/119000001… 30分钟讲解
useEffect
useEffect如果有返回值,返回的函数是在执行下一次useEffect之前或者组件卸载时调用。
useEffect在组件第一次挂载时执行一次,输出0.
多次点击加一按钮之后,输出
更新0
1
更新1
2
更新2
3
1. JSX如何进行条件判断和列表渲染
- if else
- 三元运算符
- 逻辑表达式 && ||
2. React事件
- bind this
- 关于event参数
- 传递自定义参数
juejin.cn/post/684490…
为什么React类组件中需要使用bind来绑定this
2.1 React事件和DOM事件的区别
React16 事件绑定在document上,React 17之后 React事件不再被绑定在document上,而是绑定在root容器上!!
回顾:为什么React16的时候stopPropagation无效了
事件委托的节点从 React16 的 document 更改为 React17 的 React 树的根 DOM 容器。
这一改动的出发点是如果页面中存在多个 React 应用,由于他们都会在顶层document注册事件处理器,如果你在一个 React 子应用的 React 事件中调用了e.stopPropagation(),无法阻止事件冒泡到外部树,因为真实的事件早已传播到document。
而将事件委托在 React 应用的根 DOM 容器则可以避免这样的问题,减少了多个 React 应用并存可能产生的问题,并且事件系统的运行也更贴近现在浏览器的表现。
在 React16 中,对 document 的事件委托都委托在冒泡阶段,当事件冒泡到 document 之后触发绑定的回调函数,在回调函数中重新模拟一次 捕获-冒泡 的行为,所以 React 事件中的e.stopPropagation()无法阻止原生事件的捕获和冒泡,因为原生事件的捕获和冒泡已经执行完了。
在 React17 中,对 React 应用根 DOM 容器的事件委托分别在捕获阶段和冒泡阶段。即:
-
当根容器接收到捕获事件时,先触发一次 React 事件的捕获阶段,然后再执行原生事件的捕获传播。所以 React 事件的捕获阶段调用
e.stopPropagation()能阻止原生事件的传播。 -
当根容器接受到冒泡事件时,会触发一次 React 事件的冒泡阶段,此时原生事件的冒泡传播已经传播到根了,所以 React 事件的冒泡阶段调用
e.stopPropagation()不能阻止原生事件向根容器的传播,但是能阻止根容器到页面顶层的传播。
3. React表单
- 受控组件
- 非受控组件
受控组件
受控组件:value + onChange事件
value={this.state.name} onChange={()=>{this.setState({name: e.target.value})}}
- input textarea select 用value
- checkbox radio 用checked
非受控组件
-
ref
-
defaultValue 和 defaultChecked
-
手动操作DOM元素
File类型的input,使用受控组件无法处理,必须使用DOM操作。
某些富文本编辑器,需要传入DOM元素。
- 优先使用受控组件
- 必须操作DOM时,再使用非受控组件
zh-hans.reactjs.org/docs/uncont… 官方 非受控组件
4. 组件通讯
- props传递数据
- props传递函数
- 类型检查
组件间通信一般就是四种情况,父传子,子传父,跨多级传递,同级传递。
30分钟精通十种React组件之间通信的方法
总结
-
父组件 => 子组件:
- Props
- Instance Methods
-
子组件 => 父组件:
- Callback Functions
- Event Bubbling
-
兄弟组件之间:
- Parent Component
-
不太相关的组件之间:
- Context
- Portals
- Global Variables
- Observer Pattern
- Redux等
5. setState
- 不可变值
- 可能是异步更新
- 可能会被合并
setState更改的时候应该使用不可变值。不能直接对state里面的数组进行push pop shift unshift splice等会直接改变当前数组的函数。应该使用一个临时的值,然后直接去替换原来的值。
关于setState的异步更新和合并,主要是性能的问题。 React setState合并和批量处理
一、State的更新什么时候是同步,什么时候是异步
正如我们所知,react中使用setState更新state,而state的更新大多数情况下是异步的,但是有些情况却是同步的。
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。二、为什么是异步
如果setState是同步更新state,而state的更新又会触发组件的重新渲染,那么每次setState都会渲染组件,这对性能是很大的消耗。所以react进行了setState的合并和批量延迟更新,正如官网所述:
作者:xiaowoniu
链接:juejin.cn/post/684490…
React--setState 同步与异步更新
setState第一个参数可以是对象或者函数,如果是用对象来更新,那么就会被合并,并且是异步的。如果使用回调函数来更新的话,那么就是同步的。
为什么会被合并呢,因为首先对象形式下,它是异步的,且类似于使用
Object.assign({count: 1}, {count:1}) 合并的结果就是1
this.setState({
count: count + 1 //相当于 count: 1,因为count初始是0
})
this.setState({
count: count + 1 //相当于 count: 1,因为前一个是异步的,还没更新,count初始是0
})
this.setState({
count: count + 1 //相当于 count: 1,因为前一个是异步的,还没更新,count初始是0
})
setState第二个参数是一个函数,作为更新完state之后的回调函数使用,类似于vue中的nextTick。待验证)
6. React生命周期
- componentWillMount 在渲染前调用。
- componentDidMount 在第一次渲染后调用。
- componentWillReceiveProps 在组件接收到一个新的props时被调用。这个方法在第一次渲染时不会被调用。
- shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
- componentWillUpdate 在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
- componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
- componentWillUnmount 在组件移除之前调用
7. Portals
- 组件默认会按照既定层次嵌套渲染
- 如何让组件渲染到父组件以外?
这是一个固定定位的内容
此时这个div已经不在root容器下了,而在body下。
一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:
8. Context
createContext,函数式组件使用 useContext 这个hook获取。 类式组件通过.Provider
zh-hans.reactjs.org/docs/contex…
9. 异步组件
- import()
- React.lazy
- React.Suspense
10. 性能优化
- shouldComponentUpdate
- PureComponent 和 React.memo
- 不可变值 immutable.js
10.1 SCU基本用法
React默认情况下 只要父组件有更新,那么子组件也会跟着更新。
想象这样一个场景: 一个TodoList组件,分为Header,List,Footer三个子组件。
往里添加新的todolist的时候,父组件必然更新,但是此时Footer组件中的componentDidUpdate()生命周期函数也会执行,进行更新。
默认情况下 shouldComponentUpdate()是默认返回true的 —— React默认重新渲染所有子组件
shouldComponentUpdate(){
return true
}
React为什么要这么做呢?
因为如果开发人员在使用setState的时候 违反了不可变值规则 导致出现bug
例如在修改数组的时候,使用了会改变原数组的push方法。
this.state.list.push(1)
this.setState({
list: this.state.list
})
这样的话 list原本就被改变了,相当于在setState的时候,给list赋值的时候是两边相等的。这样的话,就不会出发SCU
shouldComponentUpdate(nextProps, nextState){
if(_.isEqual(nextProps.list, this.props.list)){
// _.isEqual 做对象或者数组的深度比较(一次性递归到底)不建议使用 比较耗费性能
//不会触发 因为此时list本来就是相等的 这样就会导致在添加todoList的时候 不触发渲染。
return true
}else{
return false
}
}
因此 React为了防止因为开发人员的错误产生的bug,干脆就都放行,把性能优化的实现交给开发人员自身。
总结:
- SCU默认返回true, 即React默认重新渲染所有子组件
- 必须配合不可变值一起使用
- 可先不用SCU,有性能问题再考虑使用
10.2 PureComponent和memo
- PureComponent,SCU中实现了浅比较
- memo是函数组件中的PureComponent
- 浅比较已经适用大部分情况(尽量不要使用深度比较)
class List extends React.PureComponent
React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
React.memo 为高阶组件。
如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。
注意
与 class 组件中
shouldComponentUpdate()方法不同的是,如果 props 相等,areEqual会返回true;如果 props 不相等,则返回false。这与shouldComponentUpdate方法的返回值相反。
11. 高阶组件 关于组件公共逻辑的抽离
- mixin,已被React弃用
- HOC 高阶组件
- Render Props
11.1 高阶组件基本用法
// HOC不是一种功能,而是一种工厂模式
const HOCFactory = (Component)=>{
class HOC extends React.Component{
// 在此定义多个组件的公共逻辑
render(){
return <Component {...this.props} />
}
}
return HOC
}
const EnhancedComponent1 = HOCFactory(WrappedComponent1)
const EnhancedComponent2 = HOCFactory(WrappedComponent2)
import React from 'react'
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性
这里的props可以通过外部调用高阶组件的时候来传递
比如<HOCDemo a="100" />
*/}
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
return withMouseComponent
}
const App = (props) => {
const a = props.a
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App) // 返回高阶函数
redux connect 是高阶组件
11.2 render props
- HOC 模式简单 增加组件层级
- Render Props 代码简洁,但是学习成本比较高
- 按需使用
12. Redux
- 基本概念
- 单项数据流
- react-redux
- 异步action
- 中间件