React高级
函数组件对比class组件的区别
- 纯函数,输入props,输出JSX
- 没有实例,没有生命周期,没有state
- 不能扩展其他方法
适用场景:一些简单页面,接收props,返回jsx
非受控组件
前面基础篇讨论过受控组件主要处理表单数据,但是可能有一些更复杂的逻辑需要我们获取到组件的DOM节点,进行一些处理,就显得麻烦,比如我们的文件上传组件
、富文本
等,这时我们就可以使用非受控组件来获取组件处理。来看看实际的例子:
import React, { Component } from 'react';
export default class UncontrolledDemo extends Component {
constructor(props) {
super(props)
this.state = {
name: 'airhua'
}
// 创建ref
this.nameInput = React.createRef()
this.fileInputRef = React.createRef()
}
consoleFile = () => {
const elem = this.fileInputRef.current
console.log(elem.files[0].name);
}
render() {
return <div>
{/* 场景一般是需要操控dom */}
<input type="text" ref={this.state.nameInput} defaultValue={this.state.name} />
<input type="file" ref={this.fileInputRef} />
<button onClick={this.consoleFile}>打印文件名</button>
</div>;
}
}
ref
首先我们先在constructor
中创建好了我们的ref对象:
this.nameInput = React.createRef()
this.fileInputRef = React.createRef()
defaultValue默认值
在标签中我们使用ref等于state中定义的ref进行实例化,还可以看到相比受控组件我们多出了defaultValue,这里只是一个初始值,后面的DOM更新值后这个值并不会随之更新。
<input type="text" ref={this.state.nameInput} defaultValue={this.state.name} />
<input type="file" ref={this.fileInputRef} />
current
最后我们可以通过ref中current拿到DOM实例
consoleFile = () => {
const elem = this.fileInputRef.current
console.log(elem.files[0].name);
}
因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
Portal
创建 portal。Portal 将提供一种将子节点渲染到 DOM 节点中的方式,该节点存在于 DOM 组件的层次结构之外。
来看看例子:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
export default class ProtalsDemo extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
return ReactDOM.createPortal(
<div className="modal">{ this.props.children }</div>,
document.body
)
}
}
上面便是一个简单的Protal创建过程。
可以看到我们定义的组件并没有渲染到root节点内,这里还不得不说一下this.props.children
,你可以把它和vue中slot联系一下,它接收组件里面的内容传到到这。
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
import React, { Component } from 'react'
const ThemeContext = React.createContext('light')
function TooBar() {
return (
<ThemeButton></ThemeButton>
)
}
class ThemeButton extends Component {
// 指定contextType读取当前的theme context
static contextType = ThemeContext
render() {
// 读取到了之后就可以使用 this.context使用
const theme = this.context
return <div>
<p>当前主题 {theme}</p>
</div>
}
}
export default class ContentDemo extends Component {
constructor(props) {
super(props)
this.state = {
theme: 'light'
}
}
changeTheme = () => {
this.setState({
theme: this.state.theme === 'light' ? 'dark' : 'light'
})
}
render() {
// 使用一个 Provider 将当前theme传递给以下组件树
// 无论多深,任何组件都能读取这个值
return (
<ThemeContext.Provider value={ this.state.theme }>
<TooBar></TooBar>
<button onClick={this.changeTheme}>改变主题</button>
</ThemeContext.Provider>
)
}
}
异步组件
React.lazy+React.Suspense
实现组件的异步加载,在组件未加载出来,可以添加等待加载组件。
import React, { Component } from 'react'
// 引入异步组件
const ContextDemo = React.lazy(() => import('./ContextDemo'))
export default class LazyDemo extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
return (
<div>
<p>异步组件</p>
<React.Suspense
// 在异步组件未加载出来之前显示的loading...
fallback={<div>loading...</div>}
>
<ContextDemo></ContextDemo>
</React.Suspense>
</div>
)
}
}
React性能优化相关
shouldComponentUpdate
我们在下面写了一个例子,父组件包含两个子组件,当其中一个子组件更新时,另一个子组件数据并未改变但是却还是触发了componentDidUpdate
方法,这里我们就可以写shouldComponentUpdate
解决这个问题。
import React, { Component } from 'react'
class List extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
return (
<div>
{
this.props.list.map((item, index) => (
<li key={index}>{ item }</li>
))
}
</div>
)
}
}
class Footer extends Component {
constructor(props) {
super(props)
this.state = {}
}
// 内容未变, 但是重复渲染
componentDidUpdate() {
console.log('componentDidUpdate')
}
// 返回boolean类型 决定更新
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.text === this.props.text) {
return false
} else {
return true
}
}
render() {
return <div>
{this.props.text}
</div>
}
}
export default class SCU extends Component {
constructor(props) {
super(props)
this.state = {
list: [10, 20, 30],
text: 'footer'
}
}
add = () => {
this.setState({
list: this.state.list.concat(60)
})
}
render() {
return (
<div>
<List list={this.state.list}></List>
{/* 这里执行方法改变state */}
<button onClick={this.add}>增加</button>
<Footer text={this.state.text}></Footer>
</div>
)
}
}
PureComponent
你可以使用 React.PureComponent
来代替手写 shouldComponentUpdate
。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。当数据结构很复杂时,情况会变得麻烦。
比如上面Footer
组件可以改写成:
class Footer extends React.PureComponent {
constructor(props) {
super(props)
this.state = {}
}
// 不再执行
componentDidUpdate() {
console.log('componentDidUpdate')
}
render() {
return <div>
{this.props.text}
</div>
}
}
做这个性能提升还可以用React.memo实现,这里就不过多演示,可以访问链接了解
包括对不可变值的写法,可以通过immutable.js实现约束
组件公共逻辑的抽离
你可以想象,在一个大型应用程序中,一个逻辑可能在多个组件中重复实现,我们需要一个抽象,允许我们在一个地方定义这个逻辑,并在许多组件之间共享它。以下是两个抽离公共逻辑的方式。
高阶组件(HOC)
高阶组件是参数为组件,返回值为新组件的函数。
HOC 在 React 的第三方库中很常见,例如 Redux 的 connect
和 Relay 的 createFragmentContainer
。
下面我们来实现一个自己的HOC函数,例子为打印一个鼠标坐标:
import React, { Component } 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}>
{/* 包裹传入组件,在外层实现公共逻辑,...this.props穿透让返回组件保留原来的props */}
<Component {...this.props} mouse={this.state}></Component>
</div>
)
}
}
return withMouseComponent
}
class HOCDemo extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
const { x, y } = this.props.mouse
return (
<div>
<h1>鼠标位置: {x}, { y }</h1>
</div>
)
}
}
// 暴露的是withMouse返回的组件
export default withMouse(HOCDemo)
Render-props实现
核心是把公共逻辑封装函数作为一个props传入子组件
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class Mouse extends Component {
// render函数必须传入
static propTypes = {
render: PropTypes.func.isRequired
}
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}>
{/* 执行传入函数 */}
{this.props.render(this.state)}
</div>
)
}
}
export default class RenderPropDemo extends Component {
render() {
return (
<Mouse
// 传入函数 作为props
render={
({ x, y }) => <h1>坐标: {x}, { y }</h1>
}
></Mouse>
)
}
}
HashRouter和BrowserRouter区别
HashRouter
- 基于hash模式,页面跳转原理使用location.hash、location replace
- 适用于To B项目,部署内网,本公司业务人员使用的项目等
BrowserRouter
- 基于history模式,页面跳转原理是使用了history对象api,history.pushState、history.replaceState
- 适用于To C项目、面向大众项目,需要后端做处理
使用方法
import React from 'react'
import {
// HashRouter as Router, // hash模式
BrowserRouter as Router, // history模式
Route,
Switch,
} from 'react-router-dom'
import Home from '../containers/home'
const BasicRouter = () => (
<Router>
<Switch>
<Route exact path="/" component={Home} />
</Switch>
</Router>
)
export default BasicRouter