组件化思想的应用
尽可能的将页面拆分成一个个小的、可复用的组件。这样让我们的代码更加方便组织和管理,并且扩展性也更强。
React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:
- 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component)。
- 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component)。
- 根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container Component)。
这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:
- 函数组件、无状态组件、展示型组件主要关注UI的展示。
- 类组件、有状态组件、容器型组件主要关注数据逻辑。
类组件
类组件的定义有如下要求:
- 组件的名称是大写字符开头(无论类组件还是函数组件)
- 类组件需要继承自 React.Component
- 类组件必须实现render函数
使用class定义一个组件:
- constructor是可选的,我们通常在constructor中初始化一些数据。
- this.state中维护的就是我们组件内部的数据。
- render() 方法是 class 组件中唯一必须实现的方法。 其中render函数可以返回一下值:
- React 元素。
- 数组或 fragments:使得 render 方法可以返回多个元素。
- Portals:可以渲染子节点到不同的 DOM 子树中。
- 字符串或数值类型:它们在 DOM 中会被渲染为文本节点。
- 布尔类型或 null:什么都不渲染。
函数组件
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。
普通的函数式组件(非hooks):
- 没有生命周期,也会被更新并挂载,但是没有生命周期函数。
- 没有this(组件实例)。
- 没有内部状态(state)。
生命周期
下面来介绍一下常用的生命周期函数。
Constructor
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
constructor中通常只做两件事情:
- 通过给 this.state 赋值对象来初始化内部的state。
- 为事件绑定实例(this)。
componentDidMount
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。
componentDidMount中通常进行哪里操作呢?
- 依赖于DOM的操作可以在这里进行。
- 在此处发送网络请求就最好的地方。
- 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)。
componentDidUpdate
componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
- 当组件更新后,可以在此处对 DOM 进行操作;
- 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。
componentDidUpdate()中直接调用setState(),但请注意它必须被包裹在一个条件语句里。否则会导致死循环。
componentWillUnmount
componentWillUnmount() 会在组件卸载及销毁之前直接调用。
- 在此方法中执行必要的清理操作。 例如,清除 timer,取消网络请求或清除
- 在 componentDidMount() 中创建的订阅等。
componentWillUnmount()中不应调用setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
组件通信
父组件向子组件传递信息
将数据当成子组件的属性,然后子组件的props接收。
- 函数组件接收 函数的第一个参数就是props参数对象,可以拿到传递的属性数据。
function MyButton(props) {
console.log(props)
return (
<h1>{props.name}</h1>
)
}
- 类组件接收 通过在构造函数中调用super(props),即可获取props。
class MyButton extends React.Component {
// 也可以不写,react自动传入
// constructor(props) {
// super(props)
// }
render() {
return (
<h1>{this.props.name}</h1>
)
}
}
我们在传递props时,可以给props指定默认值,并且指定props的类型。
- 指定默认值
通过class属性
defaultProps,设置props的默认值。
static defaultProps = {
name: "=====",
age: 30
}
- 指定props的类型 安装
npm install prop-types --save
引入prop-types库
import PropTypes from 'prop-types';
函数组件
import React from 'react';
import propTypes from 'prop-types'
export default function AppTest(props) {
return (
<div>
<h1>{props.name}</h1>
</div>
)
}
AppTest.defaultProps = {
name: 11, // 这里会报警告
}
// 他也会检查默认值的类型是否正确
AppTest.propTypes = {
name: propTypes.string
}
类组件
import React from 'react';
import propTypes from 'prop-types'
export default class AppTest extends React.Component{
render() {
return (
<div>
<h1>{this.props.name}</h1>
</div>
)
}
}
AppTest.defaultProps = {
name: 11, // 这里会报警告
}
// 他也会检查默认值的类型是否正确
AppTest.propTypes = {
name: propTypes.string
}
子组件向父组件传递信息
其实就类似于回调函数,子组件通过this.props.事件调用函数,而父组件定义函数。然后将参数传递给父组件。
//父组件
state = {
color: 'red'
}
changeColor = color => {
this.setState({
color: color
})
}
render() {
// console.log(this)
return (
<div>
<h1 style={{ color: this.state.color }} >llm</h1>
<Son changeColor={this.changeColor.bind(this)}></Son>
</div>
)
}
//子组件
render() {
return (
<div>
<button onClick={() => { this.props.changeColor('blue') }}>蓝色</button>
<button onClick={() => { this.props.changeColor('red') }}>红色</button>
</div>
)
}
插槽
react中没有插槽这一概念,默认自定义组件中不能有内容。
利用props.children来实现
- 如果标签内只有一个元素,那么props.children代表就是该元素
- 如果标签内有多个元素,那么props.children是这些元素组成的数组
- 但是,我们可以在定义组件时,在内容中写上{props.children}来表示占位,当使用自定义标签时,标签里的内容会自动取代{props.children}。
- 每个组件都可以获取到
props.children:它包含组件的开始标签和结束标签之间的内容。
import React from 'react'
export default function SlotTest(props) {
return (
<div>
<div>slotTest组件中的本身标签</div>
{props.children}
</div>
)
}
import React from 'react';
import ReactDOM from "react-dom"
import SlotTest from './SlotTest'
ReactDOM.render((
<SlotTest>
<p>p标签</p>
<a href="#">a标签</a>
<div>div标签</div>
</SlotTest>
), document.getElementById("root"))
利用props传入插槽内容
- 我们也可以不用children占位,可以自定义占位的,然后通过标签属性的形式传入内容。
import React from 'react'
export default function SlotTest(props) {
return (
<div>
<div>slotTest组件中的本身标签</div>
<div>{props.leftSlot}</div>
{props.rightSlot}
</div>
)
}
import React from 'react';
import ReactDOM from "react-dom"
import SlotTest from './SlotTest'
ReactDOM.render((
<SlotTest leftSlot={<div>left标签</div>} rightSlot={<div>right标签</div>} />
), document.getElementById("root"))
跨组件通信
通过逐个组件传递props。
通过context。
使用context传递数据要经过以下几步
- 创建context实例。
注意: 这里的默认值仅会在
Consumer在组件树中无法找到匹配的Provider才会使用,因此即使你给Provider的value传入undefined值时,Consumer也不会使用默认值。
注意:这里的默认值可以接受一个对象。
let context = React.createContext('这里可以传入默认值')
- 调用Provider内部组件将值进行传递,利用value属性来传递值。 如果没写这一步,则默认找默认值。这里是将接收共享数据的组件包裹着。
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
- 指定当前读取的是哪一个context值
- React 会往上找到最近的 Provider,然后使用它的值。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
- 如果没有定义Provider,则他会使用创建context实例时指定的默认值。
static contextType = context;
完整demo
import React, { createContext } from 'react'
const ParentContext = createContext({
color: 'red',
value: 'zh======================'
})
export default class Parent extends React.Component {
render() {
return (
<div>
<ParentContext.Provider
value={{ color: 'skyblue', value: 'llm==================' }}
>
<Son />
</ParentContext.Provider>
</div>
)
}
}
class Son extends React.Component {
render() {
return (
<div>
{/* 方式一: */}
{/* <p style={{ color: this.context.color }}>{this.context.value}</p> */}
{/* 方式二: */}
<ParentContext.Consumer>
{(item) => <p style={{ color: item.color }}>{item.value}</p>}
</ParentContext.Consumer>
</div>
)
}
}
// 方式一
// Son.contextType = ParentContext
那么我们就来看如何使用它吧?
- 直接调用this.context,但是必须加上这个静态的属性
static contextType = context对象,这种使用不可以用在函数式组件中。
render() {
return <Button theme={this.context} />;
}
- 直接通过context的Consumer属性结合函数使用。这种方式是可以在函数式组件中使用的。
这种方法需要一个函数作为子元素。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
export default class MyContext extends Component {
render() {
return (
<div>
<ContextProp.Consumer>
//利用函数获取该value值。
{
value => <div>{value}</div>
}
</ContextProp.Consumer>
</div>
)
}
}
- 消费多个context
// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用户登录 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// 提供初始 context 值的 App 组件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一个组件可能会消费多个 context
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
通过以上事例,我们可以看出,在对于多个context共享时,代码非常难写,难看。所以一般共享多个数据不会去使用它,都是使用redux。
setState
将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。
为什么setState更新是异步的
- setState设计为异步,可以显著的提升性能。
- 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的。
- 最好的办法应该是获取到多个更新,之后进行批量更新。
- 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步。state和props不能保持一致性,会在开发中产生很多的问题。
如何获取异步更新后的state
可以使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。
this.setState({
pp: "++++++++++++"
}, () => {
console.log(this.state.pp) // "++++++++++++"
})
componentDidUpdate(prevProps, prevState, prev3) { // 这上面的参数都获取的是以前的值
console.log(this.state.pp) // "++++++++++++"
}
那么setState修改数据,如何让其同步获取呢
- 将setState放在定时器中更新数据
handleClick = () => {
setTimeout(() => {
this.setState({
name: 'llm'
})
}, 0)
console.log(this.state.name) // "llm"
}
- 将setState放在原生的dom事件中更新数据
componentDidMount() {
const btn = document.getElementById('btn')
btn.addEventListener('click', () => {
this.setState({
name: 'llm'
})
console.log(this.state.name) // "llm"
})
}
setState参数介绍
- 可以直接传入一个对象,来修改state的值。
this.state = {
name: "kkk",
age: 20
}
handleClick = () => {
this.setState({
name: "==="
})
// 不会立刻获取到更新后的值。
console.log(this.state.name) // kkk
}
<div>
<h1>{this.state.name}</h1>
<button onClick={this.handleClick}>按钮</button>
</div>
- 也可以传入一个updater。
updater 函数中接收的
state和props都保证为最新。updater 的返回值会与state进行浅合并。
this.state = {
name: "kkk",
age: 20
}
handleClick = () => {
this.setState((state, props) => {
return {
name: "===="
}
})
}
<div>
<h1>{this.state.name}</h1>
<button onClick={this.handleClick}>按钮</button>
</div>
setState两种参数的区别
下面通过一个小案例,来介绍一下他两的区别。点击按钮,多次调用setState方法,然后,看其num增加几。
- 传入一个对象
import React from 'react'
export default class BtnTest extends React.Component {
constructor(props) {
super(props)
this.state = {
num: 0
}
}
handleClick = () => {
this.setState({
num: this.state.num + 1
})
this.setState({
num: this.state.num + 1
})
this.setState({
num: this.state.num + 1
})
}
render() {
return (
<div>
<h1>{this.state.num}</h1>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
}
由上面可以看出,每次点击只会增加1。
- 传入一个updater函数。
import React from 'react'
export default class BtnTest extends React.Component {
constructor(props) {
super(props)
this.state = {
num: 0
}
}
handleClick = () => {
console.log('点击按钮')
this.setState((state, props) => {
return {
num: state.num + 1
}
})
this.setState((state, props) => {
return {
num: state.num + 1
}
})
this.setState((state, props) => {
return {
num: state.num + 1
}
})
}
render() {
return (
<div>
<h1>{this.state.num}</h1>
<button onClick={this.handleClick}>按钮</button>
</div>
)
}
}
从上面的结果可以看出,点击一次按钮,你会增加3。
所以传入对象作为参数,他的更新不会依据上一次的结果。但是传入一个函数作为参数,他会依据上一次结果计算。
React更新流程
React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。然后会对比旧的DOM树。
对比不同类型的元素
当节点为不同的元素,React会拆卸原有的树,并且建立起新的树。
- 当一个元素从
<a>变成<img>,从<Article>变成<Comment>都会触发一个完整的重建流程。 - 当卸载一棵树时,对应的DOM节点也会被销毁,组件实例将执componentWillUnmount() 方法。
- 建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中,组件实例将执行 componentWillMount() 方法,紧接着 componentDidMount() 方法。
对比同一类型的元素
- 当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对更新有改变的属性。
对子节点进行递归
- 在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个mutation。
- 当然如果自在最后插入一个元素,不会影响性能。但是如果在中间或者开始,或者修改元素,那么将严重影响性能。 对于上面的性能问题,我们就需要通过设置key值来避免。
render函数何时被调用
默认情况下只要修改了props,state数据,他都会被调用。如何才能有条件的使render被调用呢?
- 可以通过
shouldComponentUpdate生命周期函数返回false。并且也可以根据最新的newProps,newState的值来做出对应的需求。
shouldComponentUpdate(newProps, newState) {
// 这里都是new值。
console.log(newProps, newState)
return false
}
- 可以让class继承PureComponent,而不是Component。他可以避免我们每次手动在class中处理大量的props,state比对。PureComponent内部是通过浅比较比较新旧props, state来决定是否重新执行render函数。
- 如果想要优化函数组件,我们可以将函数组件传入memo函数。而且memo还可以传入第二个参数,表示根据什么来保证是否再次渲染该组件,如果没有传递,他依旧调用PureComponent中调用的浅层比较函数。
浅层比较函数
- 先比较新旧props或者state是否是同一个对象。
- 比较新旧props或者state其中之一是否为null。
- 比较新旧props或者state第一层属性个数是否相同。
- 比较新旧props或者state第一层属性是否相同。
所以说,我们一定不要直接修改state中的数据,如果类组件是继承PureComponent或者函数组件被memo包裹,那么如果传入的state中的数据是引用类型,将会出现不会更新界面的bug。
class ListOfWords extends React.PureComponent {
render() {
console.log('子组件')
return <div>{this.props.words.join(',')}</div>
}
}
// 如果继承Component,只要props,state改变,render就会重新渲染。
export default class PureComTest extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
words: ['marklar']
}
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// 这部分代码很糟,而且还有 bug // 这里的一个bug是使用了同一个数组。
const words = this.state.words
words.push('marklar')
this.setState({ words: words })
}
render() {
console.log('父组件')
return (
<div>
<button onClick={this.handleClick}>按钮</button>
<ListOfWords words={this.state.words} />
</div>
)
}
}
上面这个例子是不会渲染界面的。
事件总线
第三方库events。 安装
npm install events
使用
import React from 'react'
import EventEmitter from 'events'
const eventEmitter = new EventEmitter()
class Com extends React.Component {
getName(...args) {
console.log(args)
}
componentDidMount() {
// eventEmitter.addListener('name', this.getName)
eventEmitter.on('name', this.getName)
}
componentWillUnmount() {}
eventEmitter.removeListener('name')
}
render() {
return <div>子元素</div>
}
}
export default class EventBusTest extends React.Component {
handleEmit() {
eventEmitter.emit('name', 'zh', 'llm')
}
render() {
return (
<div>
<Com />
<button onClick={this.handleEmit}>传递事件</button>
</div>
)
}
}
主要的作用是实现非父子组件的通信。
ref
通过createRef来创建一个ref实例
class MyComponent extends React.Component {
constructor(props) {
super(props);
//这里创建一个ref实例
this.domRef = React.createRef();
}
componentDidMount() {
console.log('======', this.refs.stringRef) //
console.log('======', this.domRef.current) // <div>createRef获取dom</div>
}
render() {
//使ref实例绑定该元素
return <div ref={this.domRef}>createRef获取dom</div>
}
}
直接获取到该组件this.refs["ref的属性值"]
componentDidMount() {
console.log('======', this.refs.stringRef) //<div>字符串获取dom</div>
}
render() {
return (
<div ref="stringRef">字符串获取dom</div>
)
}
直接给ref传递一个函数
constructor(props) {
this.fnDom = null
}
componentDidMount() {
console.log('======', this.fnDom) // <div>通过函数获取dom</div>
}
render() {
return (
<div>
<div
ref={(dom) => {
this.fnDom = dom
}}
>
通过函数获取dom
</div>
</div>
)
}
注意我们可以在类组件中定义ref属性(上述三种方法都可以),不能在函数式组件中定义ref属性。这样会报错误。
// 当Com是类组件时,将不会出现错误。但是当Com是函数式组件时,将会出现错误。
<Com ref={this.componentDom} />
<Com ref="componentDom" />
<Com
ref={(el) => {
this.c = el
}}
/>
上面我们可以看出,想要在函数式组件中使用ref属性,我们可以使用
React.forwardRef()。
ref转发
一般我们在自定义组件中定义ref属性,那么我们也可以指定自定义组件中html元素。
- 可以将ref对象当做一个props属性传递到子组件(需要自定义ref传递的props名),然后将该props属性子组件中赋值到ref属性。注意类组件和函数组件都可以。
import React from 'react'
class Son extends React.Component {
render() {
return <div ref={this.props.refDom}>子组件</div>
}
}
// function Son(props) {
// return <div ref={props.refDom}>子组件</div>
// }
export default class Parent extends React.Component {
constructor(props) {
super(props)
this.refDom = React.createRef()
}
componentDidMount() {
console.log('==========', this.refDom.current) // <div>子组件</div>
}
render() {
return (
<div>
<Son refDom={this.refDom}></Son>
</div>
)
}
}
- 通过内置的forward函数来转发ref。这里我们可以直接设置ref作为自定义组件的props,不需要自定义ref属性名。
const ConvertComponent = React.forwardRef((props, ref) => (
<div ref={ref}>子组件</div>
))
export default class Parent extends React.Component {
constructor(props) {
super(props)
this.refDom = React.createRef()
}
componentDidMount() {
console.log('==========', this.refDom.current) // <div>子组件</div>
}
render() {
return (
<div>
<ConvertComponent ref={this.refDom} />
</div>
)
}
}
受控组件
组件的状态通过React 的状态值 state 或者 props 控制。
HTML 中,表单元素(如、 和 )之类的表单元素通常自己维护 state,并根据用户输入进行更新。
而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
- 将两者结合起来,使React的state成为“唯一数据源”。
- 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。
- 被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
select标签
单选
- value属性可以设置option的默认值。绑定state中对应的值。
- option的value属性是设置触发事件的targe.value。并更新到对应的state中。 多选
- value属性可以设置option的默认值。绑定state中对应的值。为一个数组。
- option的value属性是设置触发事件的targe.value。并更新到对应的state中。
input:text, textarea
- value属性将被作为target.value值。并更新到对应的state中。
input:checkbox, input:radio
- checked属性可以设置input的默认值,绑定state中对应的值。
- checked属性是设置触发事件的target.checked,并更新到对应的state中。 注意当使用多个input标签时,我们可以给input标签设置name属性,让我们可以通过e.target.name来复用事件处理。
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
非受控组件
组件不被 React的状态值控制。通过 dom 的特性或者React 的ref 来控制。
export default class AppTest extends React.Component {
constructor(props) {
super(props)
this.inputDom = React.createRef()
}
componentDidMount() {
this.inputDom.current.addEventListener('change', (e) => {
console.log('========', e.target.value)
})
}
render() {
return (
<div>
<form action="#">
<input type="text" ref={this.inputDom} />
</form>
</div>
)
}
}
高阶组件
高阶组件是参数为组件,返回值为新组件的函数。
- 我们可以通过displayName来给组件命名,以便React developer tools区分各个组件。
- 他可以返回函数式组件或者类组件。
- 它主要的目的是将传入的组件统一处理,然后返回。简化单一的枯燥的操作。
- 了解过express, koa等框架,可以给他理解为一个中间件。
增强props
当我们一个组件使用多次,或者多个组件都需要增加相同的props,那么我们就可以设置一个高阶组件统一做处理。
下面一个例子就是给App, App2增加age这个props。
import React from 'react'
function enhanceComponent(WrapeComponent) {
class newComponent extends React.Component {
render() {
return <WrapeComponent {...this.props} age="30" />
}
}
// newComponent.displayName = 'pp'
return newComponent
}
class App extends React.Component {
render() {
return (
<div>
App1: {this.props.name} - {this.props.age}
</div>
)
}
}
function App2(props) {
return (
<div>
App2: {props.name} - {props.age}
</div>
)
}
const EnhanceApp = enhanceComponent(App)
const EnhanceApp2 = enhanceComponent(App2)
class ParentCom extends React.Component {
render() {
return (
<div>
<EnhanceApp name="zh" />
<EnhanceApp2 name="gl" />
</div>
)
}
}
通过context增强props
通过context包裹高阶组件,共享context数据,来增强props。
import React from 'react'
const UserContext = React.createContext({
name: 'zh',
age: 20
})
function enhanceComponent(WrapeComponent) {
class newComponent extends React.Component {
render() {
return (
<UserContext.Consumer>
{(user) => {
return <WrapeComponent {...this.props} {...user} />
}}
</UserContext.Consumer>
)
}
}
// newComponent.displayName = 'pp'
return newComponent
}
class App extends React.Component {
render() {
return (
<div>
App1: {this.props.name} - {this.props.age}
</div>
)
}
}
function App2(props) {
return (
<div>
App2: {props.name} - {props.age}
</div>
)
}
const EnhanceApp = enhanceComponent(App)
const EnhanceApp2 = enhanceComponent(App2)
class ParentCom extends React.Component {
render() {
return (
<div>
<UserContext.Provider value={{ name: '===', age: 30 }}>
<EnhanceApp />
<EnhanceApp2 />
</UserContext.Provider>
</div>
)
}
}
export default ParentCom
渲染判断鉴权
就是根据props,来控制显示不同的组件。
生命周期劫持
就是把生命周期处理相同事情的内容,当在高阶组件中统一处理。
高阶组件的意义
利用高阶组件可以针对某些React代码进行更加优雅的处理。
HOC也有自己的一些缺陷:
- HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难。
- HOC可以劫持props,在不遵守约定的情况下也可能造成冲突。
Portals转移节点
某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM元素上的)。
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
- 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。
- 第二个参数(container)是一个 DOM 元素。参数一挂载的根节点。
import React from 'react'
import ReactDOM from 'react-dom'
function Modal(props) {
// 这里将modal组件中传入的内容拿过来。
return ReactDOM.createPortal(props.children, document.getElementById('modal'))
}
export default class Parent extends React.Component {
constructor(props) {
super(props)
this.refDom = React.createRef()
}
render() {
return (
<div>
<Modal>
<h1>modal标题</h1>
</Modal>
</div>
)
}
}
fragment 空标签
由于render函数或者函数组件必须只能放回单标签组件,所以我们必须用一个标签包裹众多内容,如果不想渲染这个标签,我们可以使用Fragment标签。
- Fragment 允许你将子列表分组,而无需向 DOM 添加额外节点;
- React还提供了Fragment的短语法:
<> </>。 但是,如果我们需要在Fragment中添加key,那么就不能使用短语法。
严格模式
StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。严格模式检查仅在开发模式下运行;它们不会影响生产构建。
通过React.StrictMode标签包括的react元素及其子元素都会被检查。
那么严格模式到底检查什么呢?
- 识别不安全的生命周期。
- 使用过时的ref API。
- 使用废弃的findDOMNode方法
- 在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了。
- 检查意外的副作用
- 这个组件的constructor会被调用两次。
- 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用。
- 在生产环境中,是不会被调用两次的。
- 检测过时的context API
- 早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的。
添加className的方式
- 字符串拼接 注意: 需要在每个class后面或者前面加一个空格。
<p className={'pp ' + (true ? 'active' : '')}>iiii</p>
- 数组拼接
{/* 这里的class会用,链接 */}
<p className={['title', 'active']}>oooo</p>
<p className={['title', 'active'].join(' ')}>ppppp</p>
- 通过第三方库classnames 我们知道vue中添加class属性是非常方便的。所以这个库可以让我们很方便的给dom元素添加class属性。注意undefined, null, 0, false, NaN, true传入classNames函数不会被加入className中。
<div
className={classNames(
{ active: true },
'title',
undefined,
null,
0,
NaN,
'',
true
)}
>
classnams格式
</div>
xdm,学习任何一个框架时,一定要去仔细去看官网。react官网还是挺容易搞懂的。但是对于第一次学习react的程序员来说,就比较困难了。一起加油,一起冲。