1. 类组件和函数组件
1.1 类组件
import React from "react"
// 1. 类组件
class App extends React.Component {
constructor() {
super()
this.state = {
message: "Hello React"
}
}
render() {
const { message } = this.state
return <div>{message}</div>
}
}
export default App
1.2 函数组件
function App() {
return <h2>Hello React</h2>
}
export default App
- 函数组件和类组件最主要的区别是函数组价中没有this,也没有生命周期。
2. 组件的声明周期
2.1 三个周期钩子函数
- 最常用的三个钩子函数如下:
...
componentDidMount() {
console.log("HelloWorld componentDidMount")
}
componentDidUpdate() {
console.log("HelloWorld componentDidUpdate")
}
componentWillUnmount() {
console.log("HelloWorld componentWillUnmount")
}
...
- 第一个函数执行的时机为:当组件已经挂载到DOM上时,就会被回调;
- 第二个:组件更新的时候;
- 第三个:组件即将被移除时;
2.2 声明周期图谱
-
Constructor
- 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数
- constructor中通常只做两件事情:
- 通过 this.state 赋值对象来初始化内部的 state;
- 为事件绑定实例(this);
-
componentDidMount
- componentDidMount() 会在组件挂载后(插入 DOM 树中)立即被调用
- 在此钩子中可以执行的操作:
- 依赖于 DOM 的操作可以在这里执行;
- 在此处发送网络请求;
- 可以在此添加一些订阅(需要在 componentWillUnmounted 取消订阅)
-
componentDidUpdate
- componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
- 当组件更新后,可以在此处对 DOM 进行操作;
- 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
-
componentWillUnmount
- componentWillUnmount() 会在组件卸载及销毁之前直接调用。
- 在此方法中执行必要的清理操作;例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;
3. 组件通信
3.1 父传子
- 使用 props 传参
// Main
this.state = {
banner: [123, 32, 23]
}
<MainBanner banner={banner} title="123" />
// MainBanner
render() {
const { title, banner } = this.props
return (
<div>
<h2>title: {title}</h2>
<ul>
{banner.map(item => (
<li key={item.acm}>{item.title}</li>
))}
</ul>
</div>
)
}
// 如果在传参的时候需要验证类型,需要写如下代码:
import PropTypes from "prop-types"
MainBanner.propTypes = {
banner: PropTypes.array.isRequired,
title: PropTypes.string
}
3.2 子传父
// AddCounter
import React, { Component } from "react"
export class AddCounter extends Component {
addCount(count) {
this.props.addClick(count)
}
render() {
return (
<div>
<button onClick={e => this.addCount(1)}>+1</button>
<button onClick={e => this.addCount(5)}>+5</button>
<button onClick={e => this.addCount(10)}>+10</button>
</div>
)
}
}
export default AddCounter
// App
import React, { Component } from 'react'
import AddCounter from './AddCounter'
export class App extends Component {
constructor() {
super()
this.state = {
counter: 100
}
}
changeCounter(count) {
this.setState({
counter: this.state.counter + count
})
}
render() {
const { counter } = this.state
return (
<div>
<h2>当前计数: {counter}</h2>
<AddCounter addClick={count => this.changeCounter(count)} />
</div>
)
}
}
export default App
3.3 非父子组件通信-context
- 方式一
// App
this.state = {
info: { name: 'kobe', age: 30 }
}
render {
const { info } = this.state
<Home name={"coderwhy"} age={18} />
// 可以使用这种方式一层一层传递到需要的组件
<Home {...info} />
}
// Home
render() {
const { name, age } = this.props
return (
<div>
Home: {name}-{age}
</div>
)
}
- 方式二
// 新建一个文件 theme-context.js
import React from "react"
const ThemeContext = React.createContext()
export default ThemeContext
// App
import ThemeContext from './context/theme-context'
<ThemeContext.Provider value={{ color: 'red', size: 30 }}>
<Home {...info} />
</ThemeContext.Provider>
// Home
export class Home extends Component {
render() {
const { name, age } = this.props
return (
<div>
Home: {name}-{age}
<HomeInfo />
<HomeBanner />
</div>
)
}
}
// HomeBanner
import ThemeContext from "./context/theme-context"
function HomeBanner() {
return (
<ThemeContext.Consumer>
{value => (
<h2>
Banner Info: {value.color}-{value.size}
</h2>
)}
</ThemeContext.Consumer>
)
}
export default HomeBanner
- 方式三:非父子组件需要传递多个参数
// 此时需要新建多个Context文件
// user-context
import React from "react"
const UserContext = React.createContext()
export default UserContext
// App
import ThemeContext from './context/theme-context'
import UserContext from './context/user-context'
<UserContext.Provider value={{ nickname: 'kobe', age: 100 }}>
<ThemeContext.Provider value={{ color: 'red', size: 30 }}>
<Home {...info} />
</ThemeContext.Provider>
</UserContext.Provider>
// Home
render() {
const { name, age } = this.props
return (
<div>
Home: {name}-{age}
<HomeInfo />
<HomeBanner />
</div>
)
}
// HomeInfo
render() {
return (
<div>
HomeInfo: {this.context.color}-{this.context.size}
<UserContext.Consumer>
{value => (
<h2>
HomeInfo2: {value.nickname}-{value.age}
</h2>
)}
</UserContext.Consumer>
</div>
)
}
3.4 非父子组件通信-事件总线
// event-bus.js
import { HYEventBus } from "hy-event-store"
const eventBus = new HYEventBus()
export default eventBus
// App
import React, { Component } from "react"
import Home from "./Home"
import eventBus from "./event-bus"
export class App extends Component {
constructor() {
super()
this.state = {
name: "",
age: 0,
height: 0
}
}
componentDidMount() {
eventBus.on("bannerPrev", this.prevClick, this)
eventBus.on("bannerNext", this.nextClick, this)
}
prevClick(name, age, height) {
console.log("监听到 bannerPrev")
this.setState({
name,
age,
height
})
}
nextClick(info) {
console.log("监听到 bannerNext", info)
}
componentWillUnmount() {
eventBus.off("bannerPrev", this.prevClick)
eventBus.off("bannerPrev", this.nextClick)
}
render() {
const { name, age, height } = this.state
return (
<div>
<h2>
App Component-{name}-{age}-{height}
</h2>
<Home />
</div>
)
}
}
export default App
// Home
import React, { Component } from 'react'
import HomeBanner from './HomeBanner'
export class Home extends Component {
render() {
return (
<div>
<h2>Home Component</h2>
<HomeBanner />
</div>
)
}
}
export default Home
// HomeBanner
import React, { Component } from "react"
import eventBus from "./event-bus"
export class HomeBanner extends Component {
prevClick() {
console.log("上一个")
eventBus.emit("bannerPrev", "why", 18, 1.88)
}
nextClick() {
console.log("下一个")
eventBus.emit("bannerNext", { nickname: "kobe", rank: 99 })
}
render() {
return (
<div>
<h2>HomeBanner Component</h2>
<button onClick={e => this.prevClick()}>上一个</button>
<button onClick={e => this.nextClick()}>下一个</button>
</div>
)
}
}
export default HomeBanner
4. 组件的插槽实现
4.1 插槽实现的两种方式
4.1.1 方式一
// App
<Navbar>
<div>left</div>
<h2>title</h2>
<i>right</i>
</Navbar>
// Navbar
render() {
const { children } = this.props
return (
<div className="nav-bar">
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
)
}
// 可以使用 props 类型判断来控制传入的内容是元素还是数组
Navbar.propTypes = {
// children: ProyTypes.element
children: ProyTypes.array
}
- 此方式实现的弊端:
- 当props只有一个children时,此时插槽内容默认为一个元素,而不是数组。
4.1.2 方式二
// App
<NavBar leftSlot={<button>按钮</button>} centerSlot={'中心内容'} rightSlot={<i>斜体文字</i>} />
// NavBar
render() {
const { leftSlot, centerSlot, rightSlot } = this.props
return (
<div className="nav-bar">
<div className="left">{leftSlot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightSlot}</div>
</div>
)
}
4.2 组件的作用域插槽
// App
export class App extends Component {
constructor() {
super()
this.state = {
titles: ['流行', '精选', '新款'],
tabIndex: 0
}
}
indexChange(tabIndex) {
this.setState({ tabIndex })
}
getTabItem(item) {
if (item === '流行') {
return <h2>{item}</h2>
} else if (item === '精选') {
return <button>{item}</button>
} else {
return <i>{item}</i>
}
}
render() {
const { titles, tabIndex } = this.state
return (
<div className="app">
<TabControl titles={titles} tabClick={i => this.indexChange(i)} itemType={item => this.getTabItem(item)} />
<h1>{titles[tabIndex]}</h1>
</div>
)
}
}
// TabControl
export class TabControl extends Component {
constructor() {
super()
this.state = {
currentIndex: 0
}
}
tabChange(i) {
this.setState({ currentIndex: i })
this.props.tabClick(i)
}
render() {
const { titles, itemType } = this.props
const { currentIndex } = this.state
return (
<div className="tab-control">
{titles.map((title, index) => (
<div className={`item ${currentIndex === index ? "active" : ""}`} key={title} onClick={e => this.tabChange(index)}>
{/* <span className="text">{title}</span> */}
// itemType 是一个函数,在此处是直接调用,并且将title参数传过去
{itemType(title)}
</div>
))}
</div>
)
}
}
5. setState的原理解析
5.1 setState的三种写法
- 基础方式调用
this.setState({
message: "你好呀, 李银河"
})
- setState 可以传入一个回调函数
// 好处一: 可以在回调函数中编写新的 state 逻辑
// 好处二: 当前的回调函数会将之前的 state 和 props 传递过来
this.setState((state, props) => {
// 1. 可以编写一些对新的 state 处理逻辑
// 2. 可以获取之前的 state 和 props 值
console.log(this.state.message, this.props)
return {
message: "你好呀, 李银河"
}
})
- setState中传入第二个参数
// setState 在 React 的事件处理中是一个异步调用
// 如果希望在数据更新之后(数据合并), 获取到对应的结果执行一些代码逻辑
// 那么可以在 setState 中传入第二个参数: callback
this.setState({ message: '你好呀, 李银河' }, () => {
console.log('+++++++++++', this.state.message)
})
console.log('-----------', this.state.message)
5.2 setState设计成异步
- 在 react18之前,setTimeout 中 setState 操作,是同步操作
- 在 react18之后,setTimeout 中 setState 操作,是异步操作(批处理)
- 如果希望是同步操作,执行以下函数:
import { flushSync } from "react-dom"
flushSync(() => {
this.setState({ message: "你好呀, 李银河" })
})
// 此时这个值就是上面修改后的值,而不是之前声明的message
console.log(this.state.message)
6. render函数性能优化
- shouldComponentUpdate,简称 SCU
- 本质上是通过判断shouldComponentUpdate的返回值是true还是false来决定页面是否更新,如果是true,则更新;反之,不更新。
// App
// newProps、newState 是此组件修改后的值,而 this.state, this.props 中的值是上一次的结果值
shouldComponentUpdate(newProps, newState) {
// App 进行性能优化的点
if (this.state.message !== newState.message || this.state.counter !== newState.counter) {
return true
}
return false
}
// Home
shouldComponentUpdate(newProps, nextState) {
// 自己对比 state 是否发生改变: this.state & nextState
if (this.props.messgae !== newProps.messgae) {
return true
}
return false
}
- 这种方式的写法弊端很明显:当组件内容过多时,这种方式很繁琐,每个变量都需要一个一个进行判断。
- 改进方式:
- 类组件:
- 继承自PureComponent
- export class Recommend extends PureComponent {}
- 类组件:
- 使用 memo 包裹
- const myFunc = memo(function () {})
- 类组件:
7. 数据不可变的力量
- 一般情况下,state 中的值是可以直接修改的,但是在继承 PureComponent 后,直接修改 state 中的值是没有变化的 。如果要修改,需要将 state 中的对象浅层拷贝一份,在新的对象中进行修改,之后在将修改好后的值赋值给state。
// 1. 直接修改原来的 state, 重新设置一遍
// 在 PureComponent 中是不能引起重新渲染(re-render)
this.state.books.push(newBook)
this.setState({books: this.state.books})
- 正确的修改方式
// 2. 复制一份 books, 在新的 books 中修改, 设置新的 books
const books = [...this.state.books]
books.push(newBook)
this.setState({ books })
8. ref获取DOM元素和组件
8.1 ref获取DOM
- 在 React 元素上绑定一个 ref 字符串
getNativeDOM() {
// console.log(this.refs.lwz)
}
...
<h2 ref="lwz">Hello React</h2>
<button onClick={e => this.getNativeDOM()}>获取DOM元素</button>
- 提前创建好ref对象, 将创建好的对象绑定到元素
getNativeDOM() {
console.log(this.titleRef.current)
}
...
constructor() {
super()
this.titleRef = createRef()
}
<button onClick={e => this.getNativeDOM()}>获取DOM元素</button>
<h2 ref={this.titleRef}>Hello React</h2>
- 传入一个回调函数, 在对应的元素被渲染之后,回调函数执行,并且将元素传入
constructor() {
super()
this.titleEl = null
}
getNativeDOM() {
console.log(this.titleEl)
}
...
<h2 ref={el => (this.titleEl = el)}>Hello React</h2>
<button onClick={e => this.getNativeDOM()}>获取DOM元素</button>
8.2 ref获取类组件实例
import React, { createRef, PureComponent } from "react"
export class HelloWorld extends PureComponent {
constructor() {
super()
this.state = {}
}
test() {
console.log("-----------")
}
render() {
return <h2>Hello React</h2>
}
}
export class App extends PureComponent {
constructor() {
super()
this.hwRef = createRef()
}
getComponent() {
this.hwRef.current.test()
}
render() {
return (
<div>
<HelloWorld ref={this.hwRef} />
<button onClick={e => this.getComponent()}>获取类组件实例</button>
</div>
)
}
}
export default App
8.3 ref获取函数式组件
- 需要使用到forwardRef
import React, { createRef, forwardRef, PureComponent } from 'react'
const HelloWorld = forwardRef(function (props, ref) {
return (
<div>
<h2 ref={ref}>Hello React</h2>
<p>Hello World</p>
</div>
)
})
export class App extends PureComponent {
constructor() {
super()
this.hwRef = createRef()
}
getComponent() {
console.log(this.hwRef.current)
}
render() {
return (
<div>
<HelloWorld ref={this.hwRef} />
<button onClick={e => this.getComponent()}>获取类组件实例</button>
</div>
)
}
}
export default App
9. 受控组件和非受控组件
- 两者的区别在于组件是否由 React 来控制。如果希望变成受控组件,那么需要将组件的值和 state 值关联起来,并且在组件的值变化的时候触发事件,通过此事件来修改 state 的值。
import React, { PureComponent } from "react"
export class App extends PureComponent {
constructor() {
super()
this.state = {
username: "kobe"
}
}
inputChange(event) {
console.log("inputChange: ", event.target.value)
this.setState({ username: event.target.value }, console.log(this.state.username))
}
render() {
const { username } = this.state
return (
<div>
{/* 受控组件 */}
<input type="text" value={username} onChange={e => this.inputChange(e)} />
{/* 非受控组件 */}
<input type="text" onChange={e => this.inputChange(e)} />
<h2>username: {username}</h2>
</div>
)
}
}
export default App