HOC(高阶组件)
dva 构建 react 项目 DvaJS
umi 构建项目
React 的生命周期
HOC(高阶组件)
功能:
1. 进行某些方法或是属性的复用
2. 让外层的组件替我们完成任务,那么里层组件直接使用就可以了
const NewComponent = higherOrderComponent(YourComponent);
比如,我们想要我们的组件通过自动注入一个版权信息。
案例 1
// withCopyright.js 定义一个高阶组件
import React, { Component, Fragment } from "react";
const withCopyright = WrappedComponent => {
return class NewComponent extends Component {
render() {
return (
<Fragment>
<WrappedComponent />
<div>©版权所有 千锋教育 2019 </div>
</Fragment>
);
}
};
};
export default withCopyright;
// 使用方式
import withCopyright from "./withCopyright";
class App extends Component {
render() {
return (
<div>
<h1>Awesome React</h1>
<p>React.js是一个构建用户界面的库</p>
</div>
);
}
}
const CopyrightApp = withCopyright(App);
案例 2 有参数传递
import React, { Component } from "react";
import { CSSTransition } from "react-transition-group";
import "animate.css";
export const withAnimateHoc = Comp => {
// * 定义的参数只有组件参数
return class _ extends Component {
render() {
const { flag, timeout, classNames } = this.props; //* 通过结构父组件传进来的参数,来获取所需要的变量
return (
<CSSTransition
in={flag} // ! in取值 true/false ,这个值必须是变化的
timeout={timeout} // ! 延迟时间
classNames={classNames} // ! 这里是我们具体动画的属性
unmountOnExit // ! 当exit退出时,组件卸载掉
>
<Comp />
</CSSTransition>
);
}
};
};
- 使用
import React, { Component } from "react";
import { AnimateHoc } from "../hoc/index";
import Comp from "./Comp";
import "animate.css";
import { CSSTransition } from "react-transition-group";
const Hoc = AnimateHoc(Comp); //* 高阶组件实例化,并赋值给Hoc
export default class AnimateComp extends Component {
constructor(props) {
super(props);
this.state = {
flag: true,
timeout: 200,
classNames: {
enter: "animated",
enterActive: "slideInLeft",
exit: "animated",
exitActive: "slideOutLeft"
}
};
}
changeFlag = () => {
// console.log("张浩雨: AnimateHoc", AnimateHoc)
const { flag } = this.state;
console.log("flag 已经改变");
this.setState({
flag: !flag
});
};
render() {
const { flag } = this.state;
return (
<div>
<button onClick={this.changeFlag}>
{" "}
changeFlag changeFlag changeFlag{" "}
</button>
<Hoc {...this.state}></Hoc> //组件使用,并已属性的形式进行传餐
<CSSTransition
in={flag}
timeout={2000}
classNames={{
enter: "animated",
enterActive: "rollIn",
exit: "animated",
exitActive: "rollOut"
}}
>
<p> 动画元素 </p>
</CSSTransition>
</div>
);
}
}
dva 构建 react 项目 DvaJS
- 安装 dva-cli
$ npm install dva-cli -g
$ dva -v
dva-cli version 0.9.1
- 创建新应用
$ dva new dva-quickstart
- 这会创建 dva-quickstart 目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。
然后我们 cd 进入 dva-quickstart 目录,并启动开发服务器:
$ cd dva-quickstart
$ npm start
umi 构建项目
https://pro.ant.design/docs/getting-started
- $ yarn create umi
- yarn 会先安装最新版的 create-umi,然后提供交互式的提示来创建应用。
- $ yarn //然后安装依赖,
- $ yarn start //启动项目
React 的生命周期
React 中组件有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3 之后)
注意: 生命周期钩子函数一定不要写成箭头函数
React 生命周期
- React 15.x 10 个钩子函数
- 初始化
- 运行中
- 销毁
- React 16.x 10 个钩子函数
- 初始化
- 运行中
- 销毁
- 错误处理
- React 17.x 还没有发布 【 未来版本 】 9 个钩子函数
- 初始化
- 运行中
- 销毁
- 错误处理
一、 初始化阶段
import React, { Component } from "react";
export default class LifeCompInitial extends Component {
//? React 16.x 生命周期初始化阶段
constructor(props) {
// todo 1. 继承父组件属性,然后将props赋值给this.props 2. 定义状态
console.log("1-constructor");
super(props);
this.state = {
info: "千锋教育"
};
}
componentWillMount() {
// todo 组件即将挂载 - 为组件的事件和生命周期钩子做准备
// * 类似 vue 中 beforeCrete
// * 这里我们在项目中不使用
console.log("2-componentWillMount");
}
render() {
// todo render函数作用
// * 1. 利用React.createElement() api将jsx解析为虚拟DOM对象模型
// * 2. 计算 this.state 和 this.props Í
console.log("3-render");
const { info } = this.state;
const { name } = this.props;
return (
<div>
{info}
<p> App组件传递给我一个属性: {name} </p>
</div>
);
}
componentDidMount() {
//? 组件挂在结束
//todo componentDidMount作用:
// * 1. 数据请求,数据修改
// * 2. 操作DOM
console.log("4-componentDidMount");
fetch("/mock/index.json")
.then(data => {
// console.log( "张浩雨: LifeCompInitial -> componentDidMount -> data", data )
return data.json();
})
.then(res => {
console.log("张浩雨: LifeCompInitial -> componentDidMount -> res", res);
/* 修改数据 */
this.setState({
info: res.name
});
})
.catch(err => console.log(err));
// todo DOM操作
document.querySelector("p").style.background = "#ccc";
}
}
二、 运行阶段
import React, { Component } from "react";
export default class LifeCompRunning extends Component {
constructor(props) {
super(props);
this.state = {
info: "你们是最帅最美的"
};
}
changeInfo = () => {
this.setState({
info: "你们是最苦最累"
});
};
// ! 组件的运行阶段 1. props改变触发【 外部传入 】 2. state改变触发
componentWillReceiveProps(nextProps) {
//* 即将接收新的属性 nextProps就是新的属性
//todo 作用: 新属性的接收
// ! 路由的监听, 比如当路由改变时,app项目中的头部或是底部的显示
console.log("1 - componentWillReceiveProps", nextProps);
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate");
//todo 组件是否要更新,它决定了组件是否要渲染,是react性能优化的关键
//* 一定要有返回值,返回值为true,那么更新,false,则不更新
//* 默认不写的话,就是return true
return true;
}
componentWillUpdate() {
console.log("componentWillUpdate");
// * 组件即将更新
//* 这个狗子函数做的就是更新前的准备工作
// * 这个钩子中不能使用 this.setState(), 否则会栈溢出
}
render() {
console.log("render ");
/* render 1. jsx -> VDOM对象模型 2. 计算 this.props this.state */
/* 这个钩子函数中不能使用 this.setState(),否则会造成栈溢出 */
const { info } = this.state;
const { name } = this.props;
return (
<div>
<p> 外部传入属性: {name} </p>
<hr />
<button onClick={this.changeInfo}> 修改状态 </button>
<p> {info} </p>
</div>
);
}
componentDidUpdate() {
console.log("componentDidUpdate");
//* 组件更新结束
//* 他的作用就是可以进行DOM操作
//* 这个钩子函数中不能使用 this.setState(),否则会造成栈溢出
document.querySelector("p").style.background = "red";
}
}
//17 版本
import React, { Component } from "react";
export default class lifeCompRunning17 extends Component {
// ? 未来版本的运行中生命周期钩子函数
constructor(props) {
super(props);
this.state = {
age: 18,
username: "李大傻子"
};
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("张浩雨: -> prevState", prevState);
console.log("张浩雨: -> nextProps", nextProps);
//? 可以用来监听当前组件的props 的变化情况
console.log("getDerivedStateFromProps");
return {
age: 24,
username: nextProps.name
};
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate");
return true;
}
render() {
console.log("render");
const { name } = this.props;
const { age, username } = this.state;
return (
<div>
{username}
hello {age}岁的{name}
</div>
);
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("getSnapshotBeforeUpdate");
/* 给componentDidUpdate 一个返回值 */
return "2000";
}
componentDidUpdate(a, b, c) {
console.log("componentDidUpdate");
console.log("兵哥: LifeCompRunning -> componentDidUpdate -> c", c); // c是getSnapshotBeforeUpdate的返回值
console.log("兵哥: LifeCompRunning -> componentDidUpdate -> b", b); // b是更新后的新状态
console.log("兵哥: LifeCompRunning -> componentDidUpdate -> a", a); // 变化前的属性
}
}
三、错误处理阶段
import React, { Component, logErrorToMyService } from "react";
export default class ErrorComp extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
static getDerivedStateFromError() {
//? 未来版本的错误捕获
//* 更新 state 使下一次渲染能够显示降级后的 UI
return {
hasError: false
};
}
componentDidCatch(error, info) {
//? 现在版本的错误捕获
//* 你同样可以将错误日志上报给服务器
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong</h2>;
}
return this.props.children;
}
}
//使用
import React, { Component } from "react";
import "./App.css";
import { withAnimateHOC } from "./hoc";
import Home from "./pages/Home";
import ErrorComp from "./components/ErrorComp";
const HomeHOC = withAnimateHOC(Home);
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
flag: true,
timeout: 200,
classNames: {
enter: "animated",
enterActive: "slideInLeft",
exit: "animated",
exitActive: "slideOutRight"
}
};
}
changeFlag = () => {
const { flag } = this.state;
this.setState({
flag: !flag
});
};
render() {
const { flag, timeout, classNames } = this.state;
return (
<div className="App">
<h3> 生命周期最后一个阶段 - 错误处理阶段 </h3>
<button onClick={this.changeFlag}>changeFlag</button>
<ErrorComp>
{" "}
//* 定位
<HomeHOC
flag={flag}
timeout={timeout}
classNames={classNames}
></HomeHOC>
</ErrorComp>
</div>
);
}
}
各生命周期详解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-81rkTOxU-1571741286143)(https://ftp.bmp.ovh/imgs/2019/10/c284a41f2a42840c.png)]
1. constructor(props)
React 组件的构造函数在挂载之前被调用。在实现React.Component
构造函数时,需要先在添加其他内容前,调用super(props)
,用来将父组件传来的props
绑定到这个类中,使用this.props
将会得到。
官方建议不要在`constructor`引入任何具有副作用和订阅功能的代码,这些应当使用`componentDidMount()`。白话: 继承父组件外部传递过来的属性,然后绑定到 当前 组件的 props 属性身上
this.props = props
constructor
中应当做些初始化的动作,如:初始化state
,将事件处理函数绑定到类实例上,但也不要使用setState()
。如果没有必要初始化 state 或绑定方法,则不需要构造constructor
,或者把这个组件换成纯函数写法。
- 定义状态
- 方法绑定 this
this.fn = this.fn.bind( this )
当然也可以利用props
初始化state
,在之后修改state
不会对props
造成任何修改,但仍然建议大家提升状态到父组件中,或使用redux
统一进行状态管理。
constructor(props) {
super(props);
this.state = {
isLiked: props.isLiked
};
}
2. static getDerivedStateFromProps(nextProps, prevState)
getDerivedStateFromProps` 是 react16.3 之后新增
- 在组件实例化后,和接受新的
props
后被调用。 - 他必须返回一个对象来更新状态,或者返回 null 表示新的 props 不需要任何 state 的更新。
- 如果是由于父组件的
props
更改,所带来的重新渲染,也会触发此方法。 - 调用
steState()
不会触发getDerivedStateFromProps()
。 - 之前这里都是使用
constructor
+componentWillRecieveProps
完成相同的功能的
static async getDerivedStateFromProps ( nextProps, preState ) {
console.log("张浩雨: getDerivedStateFromProps -> preState", preState)
console.log("张浩雨: getDerivedStateFromProps -> nextProps", nextProps)
//* nextProps 表示变化后的新属性 prevState表示先前的状态
//* 在组件实例化后,和接受新的props后被调用。他必须返回一个对象来更新状态
//* 这个钩子函数不能写this.setState()
const result = await request( {
url: "/mock/index.json"
} );
console.log("张浩雨: LifeCompInistial17 -> getDerivedStateFromProps -> result", result)
return { //? 更新状态的
username: result.name
}
}
3. componentWillMount() / UNSAFE_componentWillMount()
UNSAFE 前缀表示当前这个 api 已经过时了
componentWillMount()将在React未来版本(官方说法 17.0)中被弃用。 为了避免副作用和其他的订阅,官方都建议使用
componentDidMount()`代替。这个方法是用于在服务器渲染上的唯一方法。这个方法因为是在渲染之前被调用,也是惟一一个可以直接同步修改 state 的地方。
4. render()
render()方法是必需的。当他被调用时,他将计算
this.props
和this.state
,并返回以下一种类型:
- React 元素。通过 jsx 创建,既可以是 dom 元素,也可以是用户自定义的组件。
- 字符串或数字。他们将会以文本节点形式渲染到 dom 中。
- Portals【'portl】。react 16 版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在 DOM 树的任何位置。
- null,什么也不渲染
- 布尔值。也是什么都不渲染。
当返回null
,false
,ReactDOM.findDOMNode(this)
将会返回 null,什么都不会渲染。
render()
方法必须是一个纯函数,他不应该改变state
,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。
如果shouldComponentUpdate()
返回false
,render()
不会被调用。
5. componentDidMount
componentDidMount
在组件被装配后立即调用。初始化使得 DOM 节点应该进行到这里。
通常在这里进行 ajax 请求
如果要初始化第三方的 dom 库,也在这里进行初始化。只有到这里才能获取到真实的 dom.
6.componentWillReceiveProps()/UNSAFE_componentWillReceiveProps( nextProps )
官方建议使用
getDerivedStateFromProps
函数代替componentWillReceiveProps
。
当组件挂载后,接收到新的props
后会被调用。如果需要更新state
来响应props
的更改,则可以进行this.props
和nextProps
的比较,并在此方法中使用this.setState()
。 > 如果父组件会让这个组件重新渲染,即使props
没有改变,也会调用这个方法。
React 不会在组件初始化 props 时调用这个方法。调用this.setState
也不会触发。
####7.shouldComponentUpdate(nextProps, nextState)
调用shouldComponentUpdate
使 React 知道,组件的输出是否受state
和props
的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。
在渲染新的props
或state
前,shouldComponentUpdate
会被调用。默认为true
。这个方法不会在初始化时被调用,也不会在forceUpdate()
时被调用。
如果
shouldComponentUpdate()
返回false
,componentWillUpdate
,render
和componentDidUpdate
不会被调用。
官方并不建议在shouldComponentUpdate()
中进行深度查询或使用JSON.stringify()
,他效率非常低,并且损伤性能。
8. componentWillUpdate/UNSAFE*componentWillUpdate(nextProps, nextState)
在渲染新的state
或props
时,UNSAFE_componentWillUpdate
会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。 *不能在这里使用 this.setState()_,也不能做会触发视图更新的操作。如果需要更新state
或props
,调用getDerivedStateFromProps
。
9 getSnapshotBeforeUpdate()
在 react render()
后的输出被渲染到 DOM 之前被调用。
它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。
这个生命周期返回的任何值都将作为参数传递给 componentDidUpdate()。
10) componentDidUpdate(prevProps, prevState, snapshot)
在更新发生后立即调用`componentDidUpdate()`。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作 DOM。只要您将当前的 props 与以前的 props 进行比较(例如,如果 props 没有改变,则可能不需要网络请求),这也是做网络请求的好地方。
如果组件实现getSnapshotBeforeUpdate()
生命周期,则它返回的值将作为第三个“快照”参数传递给componentDidUpdate()
。否则,这个参数是undefined
。
11. componentWillUnmount()
在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount
中创建的任何监听。
12. componentDidCatch(error, info)
错误边界是 React 组件,可以在其子组件树中的任何位置捕获 JavaScript 错误,记录这些错误并显示回退 UI,而不是崩溃的组件树。
错误边界在渲染期间,生命周期方法以及整个树下的构造函数中捕获错误。
如果类组件定义了此生命周期方法 12.componentDidCatch,则它将成错误边界。在它中调用setState()
可以让你在下面的树中捕获未处理的 JavaScript 错误,并显示一个后备 UI。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。
错误边界只会捕获树中下面组件中的错误。错误边界本身不能捕获错误。
13. static getDerivedStateFromError(){ //? 未来版本的错误捕获
//* 更新 state 使下一次渲染能够显示降级后的 UI