react面试宝典
01- react中的受控组件和非受控组件的实现方式?
受控组件:表单元素能够受到state里面的数据进行控制
将state里面的数据绑定给表单元素,给表单元素注册onChange事件,通过onChange将表单元素的值同步给state
非受控组件:通过创建ref,从而获取到表单元素,通过表单元素的value方式获取到输入框的值
02 - react中实现组件通讯的方式
父传子:通过属性绑定的形式进行传递,子组件通过props形式进行接收
子传父:实际上是通过父组件给子组件传了一个回调函数,子组件通过调用父组件的回调函数,通过传参的形式进行传递
兄弟组件:将传递的数据和修改数据的方法,提到就近的父组件上面,通过父组件传值,传递数据和方法,传递给对应的兄弟组件
跨组件传值:创建一个context,通过provider和consumer分别进行传值和接收值
03 - 在使用props传值的时候,如何来实现校验传递的值的类型?
通过props-types这个插件来实现props传递值的类型校验
04 - react中创建阶段的生命周期函数?
- constructor:组件已经开始创建了
- render:组件已经开始渲染组件了
- componentDidMount:组件渲染挂载完成
05 - 在哪个阶段可以调用setState?
在constructor中:在constructor中不允许调用setState,也就是在组件挂载之前不允许调用setState
render生命周期函数中能否调用setState?
一旦调用setState就会重新调用render,调用render就又调用了setState,进入递归循环
componentDidMount:可以调用的
06 - 如果想发送请求在哪个阶段合适
componentDidMount发送请求比较合适
07 - 组件更新阶段的生命周期函数
render、componentDidUpdate
08 - 美团面试题
(子组件没有用到父组件的数据)当父组件的数据发生变化,子组件会重新触发render吗?
会重新触发
09 - 组件卸载阶段可以做什么?
componentWillUnmount
可以清除当前组件的定时器或者注册的页面滚动事件等等无意义的事件也将其卸载
10 - 在react中如何利用render-props模式封装组件
通过render-props模式封装组件主要是来封装状态和方法,至于组件的ui结构,就应该哪里用传递不同的结构
通过属性绑定的形式传递一个函数返回自定义的结构
再通过调用该函数将数据传递给父组件上面
-
定义Mouse组件来封装公共的鼠标的位置和鼠标移动的逻辑
import React from 'react' import ReactDOM from 'react-dom' // 封装鼠标信息组件 // 该组件封装复用的数据和逻辑 class Mouse extends React.Component { // 复用的鼠标位置 state = { x: 0, y: 0 } // 鼠标移动的事件函数 handleMouseMove = (e) => { this.setState({ x: e.clientX, y: e.clientY }) } // 复用:当鼠标移动的时候,获取到新的鼠标位置 componentDidMount() { window.addEventListener('mousemove',this.handleMouseMove) } render () { // 渲染的ui结构不能写死 return null } } class App extends React.Component { render () { return ( <div> <Mouse /> </div> ) } } ReactDOM.render(<App />, document.getElementById('root'))
-
动态渲染ui结构
import React from 'react' import ReactDOM from 'react-dom' // 封装鼠标信息组件 // 该组件封装复用的数据和逻辑 class Mouse extends React.Component { // 复用的鼠标位置 state = { x: 0, y: 0 } // 鼠标移动的事件函数 handleMouseMove = (e) => { this.setState({ x: e.clientX, y: e.clientY }) } // 复用:当鼠标移动的时候,获取到新的鼠标位置 componentDidMount() { window.addEventListener('mousemove',this.handleMouseMove) } render () { // 渲染的ui结构不能写死 return this.props.renderUi(this.state.x,this.state.y) } } class App extends React.Component { render () { return ( <div> {/* 这里在使用的时候希望显示文字 */} <Mouse renderUi={ (x,y)=>{ return <div>鼠标的位置:x:{x},y:{y}</div> } } /> </div> ) } } ReactDOM.render(<App />, document.getElementById('root'))
11 - 高阶组件
也是为了复用组件的状态和操作状态的逻辑
封装一个高阶组件:封装一个高阶组件实际就是封装一个函数,函数内部提供一个组件,用来复用状态和操作状态的逻辑,至于显示的ui结构,通过函数调用传递一个ui组件,将传递的ui组件进行显示,并且把数据暴漏给这个ui组件。最终这个函数返回一个新的组件
定义一个方法,该方法返回一个组件,返回的组件用来复用数据和逻辑
import React from 'react'
import ReactDOM from 'react-dom'
function withMouse() {
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
onMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount() {
window.addEventListener('mousemove',this.onMouseMove)
}
render () {
return <div>鼠标</div>
}
}
return Mouse
}
const Mouse = withMouse()
ReactDOM.render(<Mouse/>, document.getElementById('root'))
因为结构写死了,所以定义一个想渲染的结构组件,并传递给mouse组件进行展示
import React from 'react'
import ReactDOM from 'react-dom'
function withMouse(WrapperComponent) {
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
onMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount() {
window.addEventListener('mousemove',this.onMouseMove)
}
render () {
return <WrapperComponent />
}
}
return Mouse
}
const Position = ()=>(
<div>
鼠标的位置:
</div>
)
const Mouse = withMouse(Position)
ReactDOM.render(<Mouse/>, document.getElementById('root'))
最后一步再将数据传到要显示的组件上
import React from 'react'
import ReactDOM from 'react-dom'
function withMouse(WrapperComponent) {
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
onMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount() {
window.addEventListener('mousemove',this.onMouseMove)
}
render () {
return <WrapperComponent x={this.state.x} y={this.state.y}/>
}
}
return Mouse
}
const Position = (props)=>(
<div>
鼠标的位置:{props.x},{props.y}
</div>
)
const Mouse = withMouse(Position)
ReactDOM.render(<Mouse/>, document.getElementById('root'))
12 - setState更新数据是同步还是异步?
setState更新数据是异步的,如果想多个setState进行依赖,需要将setState修改成传一个回调函数的形式
this.setState(()=>{
return {}
})
this.setState(()=>{
return {}
})
13 - JSX语法转换过程
jsx还是转换为React.createElement的形式了,最终React.createElement又转换为一个React元素,也就是虚拟dom
14 - 当父级触发setState的时候
父组件调用setState的时候,父组件本身,及其所有的子组件的render都会重新执行,但是render重新执行不代表页面会重新渲染,render这个阶段会进行虚拟dom的比较的,然后进行按需更新
15 - 如何组织避免不必要的render执行
可以通过shouldComponentUpdate这个生命周期函数返回true或者false从而决定是否执行render
16 - 虚拟dom
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容
17 - Diff算法
虚拟dom高效更新执行过程
- 初次渲染时,会根据model数据创建一个虚拟DOM对象(树)
- 根据虚拟DOM生成真正的DOM,渲染到页面
- 当数据变化后,会重新根据新的数据,创建新的虚拟DOM对象(树)
- 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
- 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
- 什么是虚拟dom:用js对象来表示页面上dom元素的的样式体现
- 虚拟dom的作用:高效更新页面,还有就是让react脱离了浏览器的概念
- 怎么来高效更新页面的:就是在第一次渲染页面的时候生成一份初始的虚拟dom树,然后当数据改变之后,再生成一份虚拟dom树,然后根据新旧dom树进行对比,对比完成之后,把有区别的进行更新
- diff算法的作用就是:新旧虚拟dom树在对比的时候就是通过diff算法来对比的
- diff算法又是怎么去对比的:tree diff、component diff、element diff
18 - 为什么可以在路由组件中可以通过props调用history
因为我们把组件传递给了Route组件,Route组件内部通过this.props.component获取到传递进去的组件,获取到之后将该组件进行展示,并且为该组件传递了history等属性
// js伪代码
// 伪代码
class Route extends React.Component {
state = {
history:{
push: function(){}
}
}
render() {
const View = this.props.component
return <View history={this.state.history}/>
}
}
<Route path="/home" component={Home} />