目录
第一种 对象式 setState(stateChange, [callback])
第二种 函数式 setState(updater, [callback])
Fragment编译时把节点丢掉,不在渲染真实DOM,拥有key属性,可以参与遍历
五、Context(createContext、useContext)
一、setState的两种写法
import React, { Component } from "react"
import "./index.css"
export default class State extends Component {
state = { count: 0 }
render() {
return (
<div>
<h2>setState的两种写法</h2>
<p>{this.state.count}</p>
<button onClick={this.add}>+1</button>
</div>
)
}
}
第一种 对象式 setState(stateChange, [callback])
- stateChange: 状态改变对象
- callback: 可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
-
setState本身是同步的方法,但是引起React后续更新状态的动作是异步,所有想要知道当前的state应在callback进行输出
-
add = () => {
const { count } = this.state
this.setState({ count: count + 1 }, () => {
console.log(this.state.count)
})
}
第二种 函数式 setState(updater, [callback])
- updater: 返回stateChange对象的函数,updater可以接收到state和props
- callback: 可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用
add = () => {
this.setState((state) => ({ count: state.count + 1 }))
}
二、lazyLoad懒加载(动态加载路由组件)
1.通过React的lazy函数配合import()函数动态加载路由组件,路由组件代码会被分开打包
2.注册路由时,通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面,调慢网速查看效果
- LazyLoad组件
import React, { Component, lazy, Suspense } from "react"
import { Link, Route } from "react-router-dom"
//加载中
import Load from "./Load"
const About = lazy(() => import("./About"))
const Home = lazy(() => import("./Home"))
export default class index extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
<Link
className="list-group-item"
to="/about"
>
About
</Link>
<Link
className="list-group-item"
to="/home"
>
Home
</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
<Suspense fallback={<Load />}>
<Route
path="/about"
component={About}
/>
<Route
path="/home"
component={Home}
/>
</Suspense>
</div>
</div>
</div>
</div>
</div>
)
}
}
三、Hooks(函数式组件)
- 原先的函数式组件因为this指向问题,所有只能定义简单的组件。
- 对象中的this,指向的实例出来的对象,所以类式组件可以定义拥有状态的复杂组件。
- Hook是React 16.8.0版本增加的新特性/新语法,可以让你在函数式组件中使用 state 以及其他的 React 特性。
严格模式下this指向问题
- 普通的this指向,指向的是window
- 函数中的this指向,指向的是undefined
- 对象中的this,指向的实例出来的对象
- 构造函数中的this,指向的是实例出来的对象
- 事件中的对象this,指向的是触发该事件的对象
- 定时器中的this,指向的是window
1. useState
- 作用:让函数组件也可以有state状态, 并进行状态数据的读写操作
- 语法:useState(initialState)
- 用法:
const [state, setState] = useState(initialState)
案例
import React, { useState, useEffect } from "react"
import "./index.css"
export default function Hook() {
// 1+n次
// console.log("$")
//数组[状态,更新状态打方法]
const [count, setCount] = useState(0)
const [name, setName] = useState("tom")
// console.log(count, add)
function add() {
setCount(count + 1)
// setCount((count) => count + 1)
}
function changeName() {
setName("丽丽")
}
return (
<div>
<h2>setState的两种写法</h2>
<h3>我的名字是:{name}</h3>
<p>{count}</p>
<button onClick={add}>+1</button>
<button onClick={changeName}>换名字</button>
</div>
)
}
2. useEffect
- 作用:用于模拟类组件中的生命周期钩子,组件每渲染一次,该函数就自动执行一次
- 可以看做 react 中
- componentDidMount
- componentDidUpdate
- componentWillUnmount
- 这三个生命周期函数的组合,也就是在这三个时候会执行
- 可以看做 react 中
- 语法:useEffect(setup, dependencies?)
- setup:要执行的函数
- 副作用操作函数
- 手动更改真实DOM
- 设置订阅 / 启动定时器
- 发ajax请求数据获取
- 无需清除的副作用操作,直接在useEffect( method )中传入操作函数。取消监听只需要在method里return一个取消监听的函数即可
- 副作用操作函数
- dependencies:依赖项数组
- 每次渲染完DOM执行副作用函数时都会浅比较dependence渲染前后的值是否一致,不一致就执行副作用,反之就不执行
- 如果该dependence为一个空数组[ ],即没有传入比较变化的变量,则比较结果永远都保持不变,那么副作用逻辑就只能执行一次,在第一次render()后执行
- 用法:
useEffect(() => {
//副作用操作
const connection = createConnection(serverUrl, roomId);
connection.connect();
//取消监听只需要在method里return一个取消监听的函数即可
return () => {
connection.disconnect();
};
}, [roomId, serverUrl])
案例
- 入口文件
export const root = createRoot(document.getElementById("root"))
- Hook组件
import React, { useState, useEffect } from "react"
import "./index.css"
import { root } from "../../index"
export default function Hook() {
const [count, setCount] = useState(0)
useEffect(() => {
let timer = setInterval(() => {
setCount((count) => count + 1)
}, 1000)
// console.log("#")
return () => {
clearInterval(timer)
}
}, [])
function add() {
setCount(count + 1)
}
function unmount() {
root.unmount()
}
return (
<div>
<p>{count}</p>
<button onClick={add}>+1</button>
<button onClick={unmount}>卸载</button>
</div>
)
}
3. useRef
作用:在函数组件中存储、查找组件内的标签或任意其它数据。用于保存标签对象,功能与React.createRef()一样
语法:useRef(initValue)
用法:
const ref = useRef(initialValue)
案例
import React, { useState, useEffect, useRef } from "react"
import "./index.css"
import { root } from "../../index"
export default function Hook() {
const intervalRef = useRef("hi")
function show() {
//提示输入的回调
alert(intervalRef.current.value)
}
return (
<div>
<input
type="text"
ref={intervalRef}
/>
<button onClick={show}>展示数据</button>
</div>
)
}
四、Fragment
Fragment编译时把节点丢掉,不在渲染真实DOM,拥有key属性,可以参与遍历
补充:
<> </>空标签编译时把节点丢掉。不能参与遍历,没有key属性
import React, { Component, Fragment } from "react"
export default class Frag extends Component {
render() {
return (
<Fragment key={1}>
<input type="text" />
</Fragment>
)
}
}
五、Context(createContext、useContext)
5.1 类式组件
1. createContext
作用: 创建一个context以便组件能够 提供和读取一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
语法:createContext(defaultValue)
用法:
const SomeContext = createContext(defaultValue)
案例
import React, { Component, createContext } from "react"
import "./index.css"
//创建一个上下文
const MyContext = createContext()
const { Provider } = MyContext
export default class A extends Component {
state = { username: "Tom", age: 18 }
render() {
const { username, age } = this.state
return (
<div className="grand">
<h3>A组件</h3>
<h4>我的用户名:【{username}】</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
)
}
}
class B extends Component {
static contextType = MyContext
render() {
const { username, age } = this.context
return (
<div className="parent">
<h3>B组件</h3>
<h4>
从A组件接收到的用户名:【{username}】,年龄【{age}】
</h4>
<C />
</div>
)
}
}
class C extends Component {
static contextType = MyContext
render() {
const { username } = this.context
return (
<div className="child">
<h3>C组件</h3>
<h4>从A组件接收到的用户名:【{username}】</h4>
</div>
)
}
}
5.2 函数式组件
2. useContext
作用:可以让你读取和订阅组件中的context
用法:useContext(SomeContext)
const value = useContext(SomeContext)
案例
- 原始写法
import React, { createContext, Component } from "react"
import "./index.css"
//创建一个上下文
const MyContext2 = createContext()
const { Provider, Consumer } = MyContext2
export default class A extends Component {
state = { username: "Tom", age: 18 }
render() {
const { username, age } = this.state
return (
<div className="grand">
<h3>A组件</h3>
<h4>我的用户名:【{username}】</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
)
}
}
function B() {
//函数式读取上下文
return (
<div className="parent">
<h3>B组件</h3>
<h4>
从A组件接收到的用户名:
<Consumer>
{(value) => {
return `【${value.username}】,年龄【${value.age}】`
}}
</Consumer>
</h4>
<C />
</div>
)
}
function C() {
return (
<div className="child">
<h3>C组件</h3>
<h4>
从A组件接收到的用户名:
<Consumer>
{(value) => {
return `【${value.username}】`
}}
</Consumer>
</h4>
</div>
)
}
- useContext写法
import React, { createContext, Component, useContext } from "react"
import "./index.css"
//创建一个上下文
const MyContext3 = createContext()
const { Provider } = MyContext3
export default class A extends Component {
state = { username: "Tom", age: 18 }
render() {
const { username, age } = this.state
return (
<div className="grand">
<h3>A组件</h3>
<h4>我的用户名:【{username}】</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
)
}
}
function B() {
//函数式读取上下文
const content = useContext(MyContext3)
const total = "1001-" + content.username
return (
<div className="parent">
<h3>B组件</h3>
<h4>
从A组件接收到的用户名:【{total}】,年龄【{content.age}】
</h4>
<C />
</div>
)
}
function C() {
const content = useContext(MyContext3)
return (
<div className="child">
<h3>C组件</h3>
<h4>从A组件接收到的用户名:【{content.username}】</h4>
</div>
)
}
六、组件优化(PureComponent)
extends Component问题
问题:
- 只要执行setState(),即使不改变状态数据, 组件也会重新render()
- 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据
解决方案:当组件的state或props数据发生改变时才重新render()
方案一
- 重写shouldComponentUpdate()方法,
- 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
父组件
shouldComponentUpdate(nextProps, nextState) { return !(this.state.carname === nextState.carname) }
子组件
shouldComponentUpdate(nextProps) { return !(this.props.carname === nextProps.carname) }
方案二
使用PureComponent,重写了shouldComponentUpdate(),自动验证阀门。只有state或props数据有变化才返回true
import React, { PureComponent } from "react"
export default class Parent extends PureComponent {}
注意:
只是进行state和props数据的浅比较, 如果只是 数据对象内部数据 变了, 返回false
不要直接修改state数据, 而是要产生新数据
change = () => { //this.setState({ carname: "car" }) const obj = this.state obj.carname = "ccc" console.log(this.state === obj)//true this.setState(obj) }
七、children props和render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
children props: 通过组件标签体传入结构,子组件读不到父组件的数据
import React, { Component } from "react" import "./index.css" export default class A extends Component { render() { return ( <div className="A"> <h3>A组件</h3> <B> <C /> </B> </div> ) } } class B extends Component { state = { name: "tom" } render() { const { name } = this.state return ( <div className="B"> <h3>B组件</h3> <h4>{this.props.children}</h4> </div> ) } } class C extends Component { render() { return ( <div className="C"> <h3>C组件</h3> </div> ) } }
render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数(约定俗称的命名)属性。预留一个位置,把组件传过来,相当与vue的插槽
import React, { Component } from "react" import "./index.css" import State from "../State" export default class A extends Component { render() { return ( <div className="A"> <h3>A组件</h3> <B render={(name) => <State name={name} />}></B> </div> ) } } class B extends Component { state = { name: "tom" } render() { const { name } = this.state return ( <div className="B"> <h3>B组件</h3> {/* 预留一个位置,把state组件传过来,相当与vue的插槽 */} <h4>{this.props.render(name)}</h4> </div> ) } }
八、错误边界
- 把错误限制在某一返回内,只用于生产环境。
- 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误。
- 一般都在父组件中进行设置。getDerivedStateFromError 配合componentDidCatch:
- getDerivedStateFromError: 当子组件出现报错时,触发调用并携带错误信息
- Parent组件
import React, { Component } from "react"
import Child from "./Child"
export default class Parent extends Component {
state = {
isError: false, //用于子组件是否产生错误
}
static getDerivedStateFromProps(error) {
return { isError: error }
}
//完成渲染
componentDidCatch(error, info) {
console.log(error, info)
}
render() {
return (
<div>
{this.state.isError ? <h3>当前网络不稳定,稍后再试</h3> : <Child />}
</div>
)
}
}
- Child组件
import React, { Component } from "react"
export default class Child extends Component {
state = {
users: [
{
id: "001",
name: "tom",
age: 18,
},
{
id: "002",
name: "nancy",
age: 19,
},
{
id: "003",
name: "xx",
age: 20,
},
],
}
render() {
const { users } = this.state
return (
<div>
{users.map((obj) => {
return (
<h3 key={obj.id}>
姓名:【{obj.name}】 年龄: 【{obj.age}】
</h3>
)
})}
</div>
)
}
}
九、组件通信方式
1. 组件间的关系
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件
2. 组件间的通信方式
1. props:
(1) children props
(2) render props
2. 消息订阅-发布:
pubs-sub、event等等
3. 集中式管理:
redux、dva等等
4. Context:
生产者-消费者模式
3. 比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)