2024年最新探秘react,一文弄懂react的基本使用和高级特性,java开发工程师面试问题大全及答案大全

总结

如果你选择了IT行业并坚定的走下去,这个方向肯定是没有一丝问题的,这是个高薪行业,但是高薪是凭自己的努力学习获取来的,这次我把P8大佬用过的一些学习笔记(pdf)都整理在本文中了

《Java中高级核心知识全面解析》

小米商场项目实战,别再担心面试没有实战项目:

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

    </p>
}
componentDidUpdate() {
    console.log('footer did update')
}
shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.text !== this.props.text
        || nextProps.length !== this.props.length) {
        return true // 可以渲染
    }
    return false // 不重复渲染
}

// React 默认:父组件有更新,子组件则无条件也更新!!!
// 性能优化对于 React 更加重要!
// SCU 一定要每次都用吗?—— 需要的时候才优化(SCU即shouldComponentUpdate)

}

// 父组件
class TodoListDemo extends React.Component {
constructor(props) {
super(props)
// 状态(数据)提升
// list的数据需要放在父组件
this.state = {
list: [
{
id: ‘id-1’,
title: ‘标题1’
},
{
id: ‘id-2’,
title: ‘标题2’
},
{
id: ‘id-3’,
title: ‘标题3’
}
],
footerInfo: ‘底部文字’
}
}
render() {
return






}
onSubmitTitle = (title) => {
this.setState({
list: this.state.list.concat({
id: id-${Date.now()},
title
})
})
}
}

export default TodoListDemo


**此时浏览器的显示效果如下:**


![组件使用-props](https://img-blog.csdnimg.cn/4b4396f40246469193064dee43314bc7.gif#)


依据以上代码,我们来对 `props` 的各个类型进行介绍。


#### (1)props 传递数据


最后一个 `TodoListDemo` 是父组件,其他都是子组件。在 `Input` 组件和 `List` 组件中,我们将 `props` 属性的内容,以 `this.props.xxx` 的方式,传递给**父组件**。


#### (2)props 传递函数


`React` 在传递函数这一部分和 `vue` 是不一样的。对于 `vue` 来说,如果有一个父组件要传递函数给子组件,子组件如果想要触发这个函数,那么需要使用事件传递和 `$emit` 的方式来解决。


大家定位到 `Input` 组件中,在这里,我们将 `submitTitle` 以函数的形式,传递给父组件中的 `onSubmitTitle` 。


#### (3)props 类型检查


大家定位到两处 `props` 类型检查的地方。使用 `react` 中的 `PropTypes` ,我们可以对当前所使用的属性进行一个类型检查。比如说: `submitTitle: PropTypes.func.isRequired` 表明的是, `submitTitle` 是一个函数,并且是一个必填项。


就这样,通过上面的例子,我们学习了**属性传递**、**属性验证**以及**父组件和子组件之间怎么通过传事件的形式来进行通信**。


### 7、setState


#### (1)不可变值


所谓不可变值,即所设置的值永不改变。那这个时候,我们就需要去创建一个副本,来设置 `state` 的值。


来看几个要点:


第一点:**state** 要在构造函数中定义。**如下代码所示:**



import React from ‘react’

// 函数组件,默认没有 state
class StateDemo extends React.Component {
constructor(props) {
super(props)

    // 第一,state 要在构造函数中定义
    this.state = {
        count: 0
    }
}
render() {
    return <div>
        <p>{this.state.count}</p>
    </div>
}

}

export default StateDemo


第二点,不要直接修改 **state** ,要使用不可变值。**如下代码所示:**



import React from ‘react’

// 函数组件,默认没有 state
class StateDemo extends React.Component {
constructor(props) {
super(props)

    // 第一,state 要在构造函数中定义
    this.state = {
        count: 0
    }
}
render() {
    return <div>
        <p>{this.state.count}</p>
    	<button onClick={this.increase}>累加</button>
    </div>
}
increase= () => {
    // 第二,不要直接修改 state,使用不可变值
    // this.state.count++ // 错误写法,会直接修改原来的值
    this.setState({
        count: this.state.count + 1 // ShouldComponentUpdate → SCU
    })
}

}

export default StateDemo


大家可以看到,在上面的代码中,我们通过 `this.state({})` 这种形式,来修改 `state` 的值。值得注意的是,很多小伙伴会直接使用 `this.state.count++` 来修改 `state` 的值,这在 `react` 中是非常不允许的。因此,要注意这个要点。


第三点,在 `react` 中操作**数组**的值。**如下代码所示:**



// 不可变值(函数式编程,纯函数) - 数组
const list5Copy = this.state.list5.slice()
list5Copy.splice(2, 0, ‘a’) // 中间插入/删除
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: […this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3), // 截取
list4: this.state.list4.filter(item => item > 100), // 筛选
list5: list5Copy // 其他操作
})
// 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值


第四点,在 `react` 中操作**对象**的值。**如下代码所示:**



// 不可变值 - 对象
this.setState({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {…this.state.obj2, a: 100}
})
// 注意,不能直接对 this.state.obj 进行属性设置,即 this.state.obj.xxx 这样的形式,这种形式会违反不可变值


#### (2)可能是异步更新


`react` 中的 `state` ,有可能是异步更新。**来看一段代码:**



import React from ‘react’

class StateDemo extends React.Component {
constructor(props) {
super(props)

    this.state = {
        count: 0
    }
}
render() {
    return <div>
        <p>{this.state.count}</p>
    	<button onClick={this.increase}>累加</button>
    </div>
}
increase= () => {
    // setState 可能是异步更新(也有可能是同步更新) 
    this.setState({
        count: this.state.count + 1
    }, () => {
        // 联想 Vue $nextTick - DOM
        console.log('count by callback', this.state.count) // 回调函数中可以拿到最新的 state
    })
    console.log('count', this.state.count) // 异步的,拿不到最新值
}

}

export default StateDemo


**此时浏览器的显示效果为:**


![异步更新①](https://img-blog.csdnimg.cn/f5d75ad5f1c24c6587b7d953d111e123.gif#pic_center)


大家可以看到,`this.state` 前半部分并不能同一时间得到更新,所以它是异步操作。而后面的箭头函数中的内容可以得到**同步更新**,所以后面函数的部分是**同步操作**。




---


值得注意的是, `setTimeout` 在 `setState` 中是**同步的**。**来看一段代码:**



import React from ‘react’

class StateDemo extends React.Component {
constructor(props) {
super(props)

    this.state = {
        count: 0
    }
}
render() {
    return <div>
        <p>{this.state.count}</p>
    	<button onClick={this.increase}>累加</button>
    </div>
}
increase= () => {
    // setTimeout 中 setState 是同步的
    setTimeout(() => {
        this.setState({
            count: this.state.count + 1
        })
        console.log('count in setTimeout', this.state.count)
    }, 0)
}

}

export default StateDemo


此时,**浏览器的显示效果为:**


![异步更新②](https://img-blog.csdnimg.cn/120804caad314254962f8d3d4458704c.gif#pic_center)




---


还有一个要注意的点是,如果是自己定义的 `DOM` 事件,那么在 `setState` 中是同步的,用在 `componentDidMount` 中。


如果是销毁事件,那么用在 `componentWillMount` 生命周期中。**代码如下:**



import React from ‘react’

class StateDemo extends React.Component {
constructor(props) {
super(props)

    this.state = {
        count: 0
    }
}
render() {
    return <div>
        <p>{this.state.count}</p>
    	<button onClick={this.increase}>累加</button>
    </div>
}
bodyClickHandler = () => {
    this.setState({
        count: this.state.count + 1
    })
    console.log('count in body event', this.state.count)
}
componentDidMount() {
    // 自己定义的 DOM 事件,setState 是同步的
    document.body.addEventListener('click', this.bodyClickHandler)
}
componentWillUnmount() {
    // 及时销毁自定义 DOM 事件
    document.body.removeEventListener('click', this.bodyClickHandler)
    // clearTimeout
}

}

export default StateDemo


**此时浏览器的显示效果为:**


![异步更新③](https://img-blog.csdnimg.cn/a9bc6dc997da49459476a288c0eae005.gif#pic_center)


#### (3)可能会被合并


`setState` 在传入对象时,**更新前**会被合并。**来看一段代码:**



import React from ‘react’

class StateDemo extends React.Component {
constructor(props) {
super(props)

    this.state = {
        count: 0
    }
}
render() {
    return <div>
        <p>{this.state.count}</p>
    	<button onClick={this.increase}>累加</button>
    </div>
}
increase= () => {
    // 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
    this.setState({
        count: this.state.count + 1
    })
    this.setState({
        count: this.state.count + 1
    })
    this.setState({
        count: this.state.count + 1
    })
}

}

export default StateDemo


**此时浏览器的显示效果为:**


![传入对象,会被合并](https://img-blog.csdnimg.cn/8b0101fd34af407aa0f42be1b37b35b4.gif#pic_center)


有小伙伴可能会觉得,一下子多个三个 `setState` ,那结果应该是 `+3` 才是。但其实,如果传入的是对象,那么结果会把三个合并为一个,最终**只执行一次**。




---


还有另外一种情况,如果传入的是**函**数,那么结果不会被合并。**来看一段代码:**



import React from ‘react’

class StateDemo extends React.Component {
constructor(props) {
super(props)

    this.state = {
        count: 0
    }
}
render() {
    return <div>
        <p>{this.state.count}</p>
    	<button onClick={this.increase}>累加</button>
    </div>
}
increase= () => {
    // 传入函数,不会被合并。执行结果是 +3
    this.setState((prevState, props) => {
        return {
            count: prevState.count + 1
        }
    })
    this.setState((prevState, props) => {
        return {
            count: prevState.count + 1
        }
    })
    this.setState((prevState, props) => {
        return {
            count: prevState.count + 1
        }
    })
}

}

export default StateDemo


**此时浏览器的显示效果为:**


![传入函数,结果不会被合并](https://img-blog.csdnimg.cn/253f42fb656a47c0a7a42519f1482e58.gif#pic_center)


大家可以看到,如果传入的是**函数**,那么结果一下子就**执行三次**了。


### 8、组件生命周期


`react` 的组件生命周期,有**单组件声明周期**和**父子组件声明周期**。其中,**父子组件生命周期**与 `Vue` 类似。


这里附上一个生命周期相关的网站:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/


**下面附上生命周期的图:**


![react的生命周期](https://img-blog.csdnimg.cn/3475e5e45db54146978e5ba33b96df66.png#pic_center)


## 📖二、React高级特性


### 1、函数组件


我们先来了解 `class` 组件和函数组件分别是什么样的。先看 `class` 组件,**代码如下:**



// class 组件
class List extends React.Component {
constructor(props) {
super(props)
}
redner() {
const { list } = this.props

    return <ul>{list.map((item, index) => {
        return <li key={item.id}>
            	<span>{item.title}</span>
            </li>
    })}</ul>
}

}


**函数组件**的形式如下:



// 函数组件
function List(props) {
const { list } = this.props

return <ul>{list.map((item, idnex) => {
    return <li key={item.id}>
        	<span>{item.title}</span>
        </li>
})}</ul>

}


现在我们来梳理以下, `class` 组件和函数组件两者之间的区别。所谓函数组件,**具有以下特点:**


* 只是一个**纯函数**,它输入的是 `props` ,输出的是 `JSX` ;
* 函数组件**没有实例**,**没有生命周期**,也**没有 state** ;
* 函数组件**不能扩展其他方法**。


相反地, `class` 组件就拥有函数组件相异的特点。


### 2、非受控组件


在上述表单模块,我们谈论到了受控组件,那接下来,我们就来谈论**非受控组件**。


所谓**非受控组件**,就是 `input` 里面的值,不受到 `state` 的控制。下面我们先来看几种场景。


#### (1)input


**先来看一段代码:**



import React from ‘react’

class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ‘星期一研究室’,
flag: true,
}
this.nameInputRef = React.createRef() // 创建 ref
}
render() {
// input defaultValue
return


{/* 使用 defaultValue 而不是 value ,使用 ref */}

{/* state 并不会随着改变 */}
state.name: {this.state.name}


alert name

}
alertName = () => {
    const elem = this.nameInputRef.current // 通过 ref 获取 DOM 节点
    alert(elem.value) // 不是 this.state.name
}

}

export default App


**此时浏览器的显示效果为:**


![inputValue](https://img-blog.csdnimg.cn/4061ca8e070b46c0ad5bf8185eb48e54.gif#pic_center)


大家可以看到,如果**是非受控组件**,那么需要使用 `defaultValue` 去控制组件的值。且最终 `input` 框里面的内容不论我们怎么改变,都不会影响到 `state` 的值。


#### (2)checkbox


对于复选框 `checkbox` 来说,**先看以下代码:**



import React from ‘react’

class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ‘星期一研究室’,
flag: true,
}
}
render() {
// checkbox defaultChecked
return



state.name: { this.state.flag === true ? ‘true’ : ‘false’ }



}
}

export default App


**此时浏览器的显示效果如下:**


![checkbox](https://img-blog.csdnimg.cn/eaaf263c779c4ed7a554945989ffbe8e.gif#pic_center)


大家可以看到,复选框如果当非受控组件来使用的使用,那么使用 `defaultCkecked` 来对值进行控制。同时,我们也看到了,最终不管 `checked` 的值如何改变, `state` 的值都不受影响。


#### (3)file


**先来看一段代码:**



import React from ‘react’

class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ‘星期一研究室’,
flag: true,
}
this.fileInputRef = React.createRef()
}
render() {
// file
return



alert file

}
alertFile = () => {
const elem = this.fileInputRef.current // 通过 ref 获取 DOM 节点
alert(elem.files[0].name)
}
}

export default App


**此时浏览器的显示效果为:**


![file](https://img-blog.csdnimg.cn/1ba7144a7cfe48f5a42868731a84ffe2.gif#pic_center)


在上面的代码中,我们使用通过 `ref` 去获取 `DOM` 节点,接着去获取到文件的名字。像 `file` 这种类型的固定,值并不会一直固定的,所以也是一个**非受控组件**。


#### (4)总结梳理


`setState` 只能处理类似于前端的显示和渲染相关的,像文件上传这种交互类型的就处理不了。下面我们来梳理下**非受控组件的几大使用场景**。**具体如下:**


* 必须手动操作 `DOM` 元素, `setState` 并无法手动操作 `DOM` 元素;
* 文件上传类型 `<input type=file>` ;
* 某些富文本编辑器,需要传入 `DOM` 元素。


受控组件 vs 非受控组件的**区别如下:**


* 优先使用**受控组件**,符合 `React` 设计原则;
* 必须操作 `DOM` 时,再使用**非受控组件**。


### 3、Protals


#### (1)为什么要用 Protals ?


一般情况下,组件默认会按照**既定层次**嵌套渲染。**类似下面这样:**



Modal内容

大家可以看到,这样不断嵌套,但里面却只有一层区域的内容是有用的。从某种程度上来说,是非常不好的。那我们想做的事情是,**如何让组件渲染到父组件以外**呢?


这个时候就需要用到 `Protals` 。


#### (2)如何使用


**先来看一段代码:**



import React from ‘react’
import ReactDOM from ‘react-dom’
import ‘./style.css’

class App extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
// 正常渲染
// return


// {this.props.children} {/* vue slot */}
//

    // 使用 Portals 渲染到 body 上。
    // fixed 元素要放在 body 上,有更好的浏览器兼容性。
    return ReactDOM.createPortal(
        <div className="modal">{this.props.children}</div>,
        document.body // DOM 节点
    )
}

}

export default App


**style.css 代码如下:**



.modal {
position: fixed;
width: 300px;
height: 100px;
top: 100px;
left: 50%;
margin-left: -150px;
background-color: #000;
/* opacity: .2; */
color: #fff;
text-align: center;
}


此时,我们来看下浏览器节点的渲染效果。**具体如下:**


![Protals](https://img-blog.csdnimg.cn/dfd9a88d62b14d59ab4a02666741dea9.png#pic_center)


大家可以看到,通过使用 `ReactDOM.createPortal()` ,来创建 `Portals` 。最终 `modals` 节点成功脱离开**父组件**,并渲染到组件外部。


#### (3)使用场景


现在,我们来梳理一些 `Protals` 常见的场景。


`protals` 常用于解决一些 `css` 兼容性问题。通常使用场景有:


* `overflow:hidden;` 触发 `bfc` ;
* 父组件 `z-index` 值太小;
* `position:fixed` 需要放在 `body` 第一层级。


### 4、context


#### (1)使用场景


有时候我们经常会有一些场景出现切换的频率很频繁,比如**语言切换**、或者是**主题切换**,那如何把对应的切换信息给有效地传递给每个组件呢?


使用 `props` ,又有点繁琐;使用 `redux` ,又太小题大做了。


因此,这个需要我们可以用 `react` 中的 `context` 。


#### (2)举例阐述


**先来看一段代码:**



import React from ‘react’

// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext(‘light’)

// 底层组件 - 函数是组件
function ThemeLink (props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this

// 函数式组件可以使用 Consumer
return <ThemeContext.Consumer>
    { value => <p>link's theme is {value}</p> }
</ThemeContext.Consumer>

}

// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
render() {
const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
return


button’s theme is {theme}



}
}
ThemedButton.contextType = ThemeContext // 指定 contextType 读取当前的 theme context。

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (





)
}

class App extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: ‘light’
}
}
render() {
return <ThemeContext.Provider value={this.state.theme}>



change theme
</ThemeContext.Provider>
}
changeTheme = () => {
this.setState({
theme: this.state.theme === ‘light’ ? ‘dark’ : ‘light’
})
}
}

export default App


**此时浏览器的显示效果为:**


![Context](https://img-blog.csdnimg.cn/cb4ed720010e44f78ac88a2f0be8d547.gif#pic_center)


在上图中,我们做到了主题的切换。现在,我们来分析下上述的代码。


首先,我们创建了一个 `Context` ,也就是 `ThemeContext` ,并传入了 `light` 值。


其次,核心在 `<Toolbar />` 组件。 `Toolbar` 现有组件为 `ThemedButton` 和 `ThemeLink` 。其中,我们先指定 `ThemedButton` 的 `contextType` 去读取当前的 `ThemeContext` ,那么就取到了默认值 `light` 。


接着,来到了 `ThemeLink` 组件。 `ThemeLink` 是一个**函数式组件**,因此,我们可以直接使用 `ThemeContext.Consumer` 来对其进行传值。


上面两个组件的值都取到了,但那只是 `ThemeContext` 的初始值。取到值了之后呢,我们还要修改值, `React` 会往上找到最近的 `ThemeContext.Provider` ,通过 `value={this.state.theme}` 这种方式,去修改和使用 `ThemeContext` 最终使用的值 。


### 5、异步组件(懒加载)


在项目开发时,我们总是会不可避免的去加载一些大组件,这个时候就需要用到**异步加载**。在 `vue` 中,我们通常使用 `import()` 来加载异步组件,但在 `react` 就不这么使用了。


`React` 通常使用 `React.lazy` 和 `React.Suspense` 来加载大组件。


**如下代码所示:**



import React from ‘react’

const ContextDemo = React.lazy(() => import(‘./ContextDemo’))

class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return


引入一个动态组件




<React.Suspense fallback={
Loading…
}>

</React.Suspense>

    // 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 的网速,Performance的network)
    // 2. 看 network 的 js 加载
}

}

export default App


首先我们使用 `import` 去导入我们要加载的组件。之后使用 `React.lazy` 去将这个组件给进行注册,也就是 `ContextDemo` 。最后使用 `React.Suspense` 来加载 `ContextDemo` 。至此,我们完成了该异步组件的加载。


### 6、性能优化


#### (1)shouldComponentUpdate(简称SCU)


**先来看下面这一段代码:**



shouldComponentUpdate(nextProps, nextState) {
if (nextState.count !== this.state.count
|| nextProps.text !== this.props.length) {
return true // 可以渲染
}
return false // 不重复渲染
}


在 `React` 中,默认的是,当父组件有更新,子组件也无条件更新。那如果每回都触发更新,肯定不太好。


因此,这个时候我们需要用到 `shouldComponentUpdate` ,判断当属性有发生改变时,可以触发渲染。当属性不发生改变时,也就是前后两次的值相同时,就不触发渲染。


那这个时候我们 需要**思考一个问题**: `SCU` 一定要每次都用吗?答案其实**不是肯定的**。


我们会去用 `SCU` ,从某种层面上来讲就是**为了优化**。因此,我们需要依据当前的开发场景,有需要的时候再去优化。


现在,我们来总结一下 `SCU` 的使用方式,**具体如下:**


* `SCU` **默认**返回 `true` ,即 `React` 默认重新渲染所有子组件;
* 必须配合 **“不可变值”** 一起使用;
* 可先不用 `SCU` ,有性能问题时再考虑使用。


#### (2)PureComponent和React.memo


`PureComponent` 在 `react` 中的使用形式如下:



class List extends React.PureComponent {
constructor(props) {
super(props)
}
render() {
const { list } = this.props

    return <ul>{list.map((item, index) => {
        return <li key={item.id}>
            <span>{item.title}</span>
        </li>
    })}</ul>
}
shouldComponentUpdate() {/\*浅比较\*/}

}


如果我们使用了 `PureComponent` ,那么 `SCU` 会进行**浅层比较**,也就是一层一层的比较下去。




---


下面我们来看 `memo` 。 `memo` ,顾名思义是**备忘录**的意思。在 `React` 中的**使用形式如下:**



function MyComponent(props) {
/* 使用props 渲染 */
}

function areEqual(prevProps, nextProps) {
/*
如果把 nextProps传入render方法的返回结果 与
preProps传入render方法的返回结果 一致的话,则返回true,
否则返回false
*/
}
export default React.memo(MyComponent, areEqual);


`memo` ,可以说是函数组件中的 `PureComponent` 。同时,使用 `React.memo()` 的形式,将我们的**函数组件**和 `areEqual` 的值进行比较,最后返回一个**新的函数**。


值得注意的是,在 `React` 中,**浅比较已经使用于大部分情况**,一般情况下,**尽量不要做深度比较**。


#### (3)不可变值


在 `React` 中,用于做不可变值的有一个库: `Immutable.js` 。这个库**有以下几大特点:**


* 彻底拥抱“不可变值”
* 基于共享数据(不是深拷贝),速度好
* 有一定的学习和迁移成本,按需使用


**下面来看一个使用例子:**



const map1 = Immutable.Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set(‘b’, 50)
map1.get(‘b’) // 2
map2.get(‘b’) // 50


基本上现在在开发中都用这个库来处理**不可变值**的问题。在实际使用中,可以看官方文档**按需使用**即可。


### 7、关于组件公共逻辑的抽离


在 `React` 中,对于组件公共逻辑的抽离主要有三种方式要了解。**具体如下:**


* `mixin` ,已被 `React` 弃用
* 高阶组件 `HOC`
* Render Props


下面将讲解高阶组件 `HOC` 和 `Render Props` 。


#### (1)高阶组件 HOC


**先看一段代码:**



// 高阶组件不是一种功能,而是一种设计模式
// 1.传入一个组件 Component
const HOCFactory = (Component) => {
class HOC extends React.Component {
// 在此定义多个组件的公共逻辑
render() {
// 2.返回拼接的结果
return <Component {this.props} />
}
}
return HOC
}
const EnhancedComponent1 = HOCFactory(WrappedComponent1)
const EnhancedComponent2 = HOCFactory(WrappedComponent2)


高阶组件 `HOC` 是**传入**一个组件,**返回**一个新的组件,见上方代码的 `1和2` 。




---


下面来看一个例子,**如下代码所示:**



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 属性 \*/}
                <Component {...this.props} mouse={this.state}/>
            </div>
        )
    }
}
return withMouseComponent

}

const App = (props) => {
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: ‘500px’ }}>

The mouse position is ({x}, {y})



)
}

export default withMouse(App) // 返回高阶函数


**此时浏览器的显示结果为:**


![高阶组件HOC](https://img-blog.csdnimg.cn/276b1f2d1e8443bb966d4f879b069e38.gif#pic_center)


在上面的代码中,我们用 定义了高阶组件 `withMouse` ,之后它通过 `<Component {...this.props} mouse={this.state}/>` 这种形式,将参数 **props** 和 **props 的 mouse 属性**给透传出来,供子组件 `App` 使用。




---


值得注意的是,在 `react` 中,还有一个比较常见的高阶组件是 `redux connect` 。**用一段代码来演示:**



import { connect } from ‘react-redux’;

// connect 是高阶组件
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)

export default VisibleTodoList


现在,我们来看下 `connect` 的源码,**具体如下:**



export const connect =(mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
constructor() {
super()
this.state = {
allProps: {}
}
}
/* 中间省略 N 行代码 */
render () {
return <WrappedComponent {…this.state.allProps} />
}
}
return Connect
}


大家可以看到, `Connect` 也是同样地,传入一个组件,并返回一个组件。


#### (2)Render Props


**先来看一段代码:**



// Render Props 的核心思想
// 1.通过一个函数,将class组件的state作为props,传递给纯函数组件
class Factory extends React.Component {
constructor() {
tihs.state = {
/* state 即多个组件的公共逻辑的数据 */
}
}
/* 2.修改 state */
render() {
return

{this.props.render(this.state)}

}
}

const App = () => {
// 3.在这里使用高阶组件,同时将高阶组件中的render属性传递进来
<Factory render={
/* render 是一个函数组件 */
(props) =>

{props.a}{props.b} …


} />
}

export default App;


在上面的高阶组件 `HOC` 中,最终返回的结果也是一个高阶组件。但在 `Render Props` 中,我们把 `Factory` 包裹在定义的 `App` 组件中,最终再把 `App` 返回。


值得注意的是,在 `Vue` 中有类似于高阶组件的用法,但没有像 `Render Props` 类似的用法,这一点需要稍微留意一下。




---


下面来看一个例子,**具体代码如下:**



import React from ‘react’
import PropTypes from ‘prop-types’

class Mouse 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}>
        {/\* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) \*/}
        {this.props.render(this.state)}
    </div>
  )
}

}
Mouse.propTypes = {
render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}

const App = (props) => (
<div style={{ height: ‘500px’ }}>
<Mouse render={
/* render 是一个函数组件 */
({ x, y }) =>

The mouse position is ({x}, {y})


}/>

</div>

)

/**
* 即,定义了 Mouse 组件,只有获取 x y 的能力。
* 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
*/

export default App


此时,浏览器的**显示效果如下:**


![Render Props](https://img-blog.csdnimg.cn/5f63b736f6784c8a8c50b9258e95bada.gif#pic_center)


在上面的代码中,通过 `this.props.render(this.state)` 这种形式,将 `Mouse` 组件中的属性传递给 `App` ,并让 `App` 成功使用到 `Mouse` 的属性值。


#### (3)HOC vs Render Props


现在,我们来梳理下 `HOC` 和 `Render Props` 的区别,**具体如下:**


* **HOC**:模式简单,但会增加**组件层级**
* **Render Props**:代码简洁,学习成本较高
* 各有各的优缺点,根据实际场景**按需使用**即可


## 📚三、Redux和React-router


### 1、Redux


#### (1)Redux概念简述


对于 `react` 来说,它是一个非视图层的轻量级框架,如果要用它来传递数据的话,则要先父传子,然后再慢慢地一层一层往上传递。


但如果用 `redux` 的话,假设我们想要**某个组件的数据**,那这个组件的数据则会通过 `redux` 来存放到 `store` 中进行管理。之后呢,通过 `store` ,再来将数据一步步地往下面的组件进行传递。


值得注意的是,我们可以视 `Redux` 为 `Reducer` 和 `Flux` 的结合。


#### (2)Redux的工作流程


`Redux` ,实际上就是一个**数据层**的框架,它把所有的数据都放在了 `store` 之中。**我们先来看一张图:**


![Redux的工作流程](https://img-blog.csdnimg.cn/img_convert/d85d3ecf4dbdfcedae11f70378161b06.png)


大家可以看到中间的 `store` ,它里面就存放着**所有的数据**。继续看 `store` 向下的箭头,然后呢,每个组件都要向 `store` 里面去拿数据。


我们用一个例子来梳理整张图,**具体如下:**


* ①整张图上有一个 `store` ,它存放着所有的数据,也就是**存储数据的公共区域**;
* ②每个组件,都要从 `store` 里面拿数据;
* ③假设现在有一个场景,模拟我们要在图书馆里面借书。那么我们可以把 `react Component` 理解为借书人,之后呢,借书人要去找图书馆管理员才能借到这本书。而借书这个过程中数据的传递,就可以把它视为是 `Action Creators` ,可以理解为 **“你想要借什么书”** 这句话。
* ④ `Action Creatures` 去到 `store` 。这个时候我们把 `store` 当做是**图书馆管理员**,但是,图书馆管理员是没有办法记住所有图书的数据情况的。一般来说,它都需要一个记录本,你想要借什么样的书,那么她就先查一下;又或者你想要还什么书,她也要查一下,需要放回什么位置上。
* ⑤这个时候就需要跟 `reducers` 去通信,我们可以把 `reducers` 视为是一个**记录本**,图书馆管理员用这个**记录本**来记录需要的数据。管理员 `store` 通过 `reducer` 知道了应该给借书人 `Components` 什么样的数据。


#### (2)react-redux


`React-redux` 中要了解的几个点是 `Provider` 、 `Connect` 、 `mapStateToProps` 和 `mapDisptchToProps` 。


**来看以下代码:**



import React from ‘react’
import { Provider } from ‘react-redux’
import { createStore } from ‘redux’
import todoApp from ‘./reducers’
import App from ‘./components/App’

let store = createStore(todoApp)

export default function() {
return


}


`react-redux` 提供了 `Provider` 的能力,大家可以看到最后部分的代码, `Provider` 将 `<App />` 包裹起来,其实也就是说为它包裹的所有组件提供 `store` 能力,这也是 `Provider` 发挥的作用。


**再来看一段代码:**



import { connect } from ‘react-redux’
import { toggleTodo } from ‘…/actions’
import TodoList from ‘…/components/TodoList’

// 不同类型的 todo 列表
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case ‘SHOW_ALL’:
return todos
case ‘SHOW_COMPLETED’:
return todos.filter(t => t.completed)
case ‘SHOW_ACTIVE’:
return todos.filter(t => !t.completed)
}
}

const mapStateToProps = state => {
// state 即 vuex 的总状态,在 reducer/index.js 中定义
return {
// 根据完成状态,筛选数据
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}

const mapDispatchToProps = dispatch => {
return {
// 切换完成状态
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}

// connect 高阶组件,将 state 和 dispatch 注入到组件 props 中
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)

export default VisibleTodoList


在上面的代码中, `connect` 将 `state` 和 `dispatch` 给注入到组件的 `props` 中,将属性传递给到 `TodoList` 组件。


#### (3)异步action


`redux` 中的**同步** `action` 如下代码所示:



// 同步 action
export const addTodo = text => {
// 返回 action 对象
return {
type: ‘ADD_TODO’,
id: nextTodoId++,
text
}
}


`redux` 中的**异步** `action` 如下代码所示:



// 异步 action
export const addTodoAsync = text => {
// 返回函数,其中有 dispatch 参数
return (dispatch) => {
// ajax 异步获取数据
fetch(url).thne(res => {
// 执行异步 action
dispatch(addTodo(res.text))
})
}
}


#### (4)Redux数据流图


`Redux` 的单项数据流图如下所示:


![在这里插入图片描述](https://img-blog.csdnimg.cn/7df2d40cde164f41999e588ddd15e8fa.png#pic_center)


关于 `Redux` 更详细内容,可查看这篇文章:[Redux从入门到进阶,看这一篇就够了!](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)


### 2、React-router


#### (1)路由模式


`React-router` 和 `vue-router` 一样,都是**两种模式**。**具体如下:**


* `hash` 模式(默认),如 `http://abc.com/#/user/10`
* `H5 history` 模式,如 `http://abc.com/user/20`
* 后者需要 `server` 端支持,因此无特殊需求可选择前者


**hash模式的路由配置如下代码所示:**



import React from ‘react’
import {
HashRouter as Router,
Switch,
Route
} from ‘react-router-dom’

function RouterComponent() {
return(













)
}


**History模式的路由配置如下:**



import React from ‘react’
import {
BrowserRouter as Router,
Switch,
Route
} from ‘react-router-dom’

function RouterComponent() {
return(













)
}


注意,`hash` 和 `history` 的区别在于 **import** 中的 `HashRouter` 和 `BrowserRouter` 。


关于 `hash` 和 `history` 相关的内容,进一步了解可查看这篇文章:[浅谈前端路由原理hash和history](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)


#### (2)路由配置


##### Ⅰ. 动态路由


假设现在有父组件 `RouterComponent` ,**具体代码如下:**



function RouterComponent() {
return(













)
}


其中,在这个组件中还有一个 `Project` 组件,需要进行动态传参。




---


继续,我们来看下子组件 `Project` 组件时如何进行**动态传参**的。**具体代码如下:**



import React from ‘react’
import { Link, useParams } from ‘react-router-dom’

function Project() {
// 获取 url 参数,如 ‘/project/100’
const { id } = useParams()
console.log(‘url param id’, id)

return (
	<div>
    	<Link to="/">首页</Link>
    </div>
)

}


大家可以看到,在 `React` 中,通过 `const { id } = useParams()` 这样的形式,来进行动态传参。




---


还有另外一种情况是**跳转路由**。**请看以下代码:**



import React from ‘react’
import { useHistory } from ‘react-router-dom’

function Trash() {
let history = useHistory()
function handleClick() {
history.push(‘/’)
}
return (


回到首页

)
}


大家可以看到,通过使用 `useHistory` ,让点击事件跳转到首页中。


##### Ⅱ. 懒加载



import { BrowserRouter as Router, Route, Switch } from ‘react-router-dom’;
import React, { Suspence, lazy } from ‘react’;

const Home = lazy(() => import(‘./routes/Home’));
const About lazy(() => import(‘./routes/About’));

const App = () => {

<Suspense fallback={

Loading……
}>






}


在 `React` 中,我们可以直接用 `lazy()` 包裹,对页面的内容进行懒加载。当然,还有另外一种情况是,加载类似于首页初次加载页面 `Loading` 的那种效果,在 `react` 中可以使用 `<Suspense>` 来解决。


## 🗞️四、结束语


在上面的文章中,我们讲解了 `react` 的基本使用以及高级特性。同时,还讲解了 `react` 的周边插件, `Redux` 和 `React-router` 。


前端在做 `react` 的项目时,总是脱离不开以上文章所涉及到的知识点,唯一的区别在于基本使用的内容用的较多,而高级特性的使用场景相对会少一些。


希望通过上文的讲解,小伙伴们有所收获🥂



> 
> * 关注公众号 **星期一研究室** ,第一时间关注学习干货,更多 **「offer来了」** 面试专栏待你解锁~
> * 如果这篇文章对你有用,记得**留个脚印jio**再走哟~
> * 我们下期见!🔑🔑🔑
> 
> 
> 





### 最后

毕竟工作也这么久了 ,除了途虎一轮,也七七八八面试了不少大厂,像阿里、饿了么、美团、滴滴这些面试过程就不一一写在这篇文章上了。我会整理一份详细的面试过程及大家想知道的一些问题细节

### 美团面试经验
![美团面试](https://img-blog.csdnimg.cn/img_convert/58c9c7be95e4a0a67d3fc53bdc3391b2.webp?x-oss-process=image/format,png)
字节面试经验
![字节面试](https://img-blog.csdnimg.cn/img_convert/08f456f96e6a71a341ec516291532dee.webp?x-oss-process=image/format,png)
菜鸟面试经验
![菜鸟面试](https://img-blog.csdnimg.cn/img_convert/55512e9142fbfd1ecb093316b1a8e8e6.webp?x-oss-process=image/format,png)
蚂蚁金服面试经验
![蚂蚁金服](https://img-blog.csdnimg.cn/img_convert/2c98fd861d16790256b740781ff3029f.webp?x-oss-process=image/format,png)
唯品会面试经验
![唯品会](https://img-blog.csdnimg.cn/img_convert/d5e8a47eabee4d7f67f3f69715ee73e6.webp?x-oss-process=image/format,png)

>因篇幅有限,图文无法详细发出


> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**

**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

ory.push('/')
    }
    return (
    	<div>
        	<Button type="primary" onClick={handleClick}>回到首页</Button>
        </div>
    )
}

大家可以看到,通过使用 useHistory ,让点击事件跳转到首页中。

Ⅱ. 懒加载
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspence, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About lazy(() => import('./routes/About'));

const App = () => {
    <Router>
    	<Suspense fallback={<div>Loading……</div>}>
        	<Switch>
        		<Route exact path="/" component={Home}/>
                 <Route path="/about" component={About}/>
        	</Switch>
        </Suspense>    
    </Router>
}

React 中,我们可以直接用 lazy() 包裹,对页面的内容进行懒加载。当然,还有另外一种情况是,加载类似于首页初次加载页面 Loading 的那种效果,在 react 中可以使用 <Suspense> 来解决。

🗞️四、结束语

在上面的文章中,我们讲解了 react 的基本使用以及高级特性。同时,还讲解了 react 的周边插件, ReduxReact-router

前端在做 react 的项目时,总是脱离不开以上文章所涉及到的知识点,唯一的区别在于基本使用的内容用的较多,而高级特性的使用场景相对会少一些。

希望通过上文的讲解,小伙伴们有所收获🥂

  • 关注公众号 星期一研究室 ,第一时间关注学习干货,更多 「offer来了」 面试专栏待你解锁~
  • 如果这篇文章对你有用,记得留个脚印jio再走哟~
  • 我们下期见!🔑🔑🔑

最后

毕竟工作也这么久了 ,除了途虎一轮,也七七八八面试了不少大厂,像阿里、饿了么、美团、滴滴这些面试过程就不一一写在这篇文章上了。我会整理一份详细的面试过程及大家想知道的一些问题细节

美团面试经验

[外链图片转存中…(img-Rsfhv27T-1715248327840)]
字节面试经验
[外链图片转存中…(img-f688LKkm-1715248327841)]
菜鸟面试经验
[外链图片转存中…(img-W8TK23Va-1715248327841)]
蚂蚁金服面试经验
[外链图片转存中…(img-1QJVBSPp-1715248327841)]
唯品会面试经验
[外链图片转存中…(img-ibmc6ocK-1715248327842)]

因篇幅有限,图文无法详细发出

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值