# react基础2
# PropTypes 与 DefaultProps
cnpm i prop-types --save
- ToDisplay.
propTypes注意使用的时候首字母小写
//给父组件传过来的内容增加校验
import PropTypes from 'prop-types'
import React, { Component} from 'react';
class ToDisplay extends Component{
render(){
return(....)
}
}
//子组件的名字ToDisplay
ToDisplay.propTypes={
deleteli:PropTypes.func,
index:PropTypes.number.isRequired,
//isRequired:必须
content:PropTypes.string
}
//若父组件未传值则增加默认值防止报warning
ToDisplay.defaultProps={
index:1000
}
如果可能是多个类型,可以使用一下的arrayOf
ToDisplay.propTypes={
deleteli:PropTypes.func,
index:PropTypes.oneOfType([PropTypes.number,PropTypes.string])
}
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的必需数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
# react函数式组件props类型约束
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
//方法1:解构写在参数上,推荐
// function HelloWorldComponent({ name ='zhangsan' }) {
// return (
// <div>Hello, {name}</div>
// )
// }
//方法2:不推荐
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
当父组件中已经给子组件设置了默认值,子组件再去设置的 所有默认都无效
// lifefather.js
import Lifechildren from './lifechildren'
//方法1:不推荐
Lifechildren.defaultProps ={
name:'jack',
// title : "百度科技父亲定义的",
}
class List extends Component {
//方法2:推荐
static defaultProps = {
pageSize: 10
}
render() {
return (
<div>
此处展示props的默认值:{this.props.pageSize}
</div>
)
}
}
# diff算法
- 同级比较(速度非常快)
- key值比对
- 多次结果一次替换,提升性能(setState异步操作的原因)
# ref的使用 React之ref操作DOM
reference简写,引用的意思,是React提供的用来操纵React类组件实例或者直接操作DOM元素的接口,尽量不使用。
它通常用于以下场景:
直接操作 DOM:在某些情况下,你可能需要
直接访问和操作 DOM 元素,例如设置焦点、测量元素的大小或位置等。通过 ref,你可以获取到这些 DOM 元素的引用,然后调用它们的原生方法或访问它们的属性。访问类组件实例:在类组件中,ref 可以用来访问组件的实例,从而可以调用
实例上的方法。
在函数组件中,通常使用 useRef 来创建 ref;在类组件中,可以使用 createRef 或在构造函数中初始化 ref。
import React, { useRef, useEffect } from 'react';
function MyComponent() {
// 创建一个 ref
const inputRef = useRef(null);
useEffect(() => {
// 在组件挂载后,我们可以通过 ref 访问 DOM 元素
if (inputRef.current) {
inputRef.current.focus(); // 设置输入框焦点
}
}, []); // 空数组表示这个 effect 只在组件挂载和卸载时运行
return (
<div>
<input ref={inputRef} type="text" placeholder="Focus me on mount" />
</div>
);
}
export default MyComponent;
React.createRef() 函数创建的对象、或者一个回调函数、或者一个字符串(遗留 API)。当 ref 属性是一个回调函数时,此函数会(根据元素的类型)接收底层 DOM 元素或 class 实例作为其参数。这能够直接访问 DOM 元素或组件实例。
class MyClassComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
if (this.myRef.current) {
this.myRef.current.focus();
}
}
render() {
return <input ref={this.myRef} type="text" />;
}
}
import React, { Component, createRef } from 'react';
// 类组件
class MyClassComponent extends Component {
myMethod() {
console.log('My method was called!');
// 这里可以执行其他逻辑
}
render() {
return <div>This is a class component</div>;
}
}
// 父组件
class ParentComponent extends Component {
constructor(props) {
super(props);
// 创建一个 ref
this.myClassComponentRef = createRef();
}
componentDidMount() {
// 通过 ref 访问类组件实例,并调用其方法
this.myClassComponentRef.current.myMethod();
}
render() {
return (
<div>
<MyClassComponent ref={this.myClassComponentRef} />
</div>
);
}
}
export default ParentComponent;
- 回调 Refs
- React.createRef() 创建引用
和vue2那样this.$refs.xx的使用方法已经被废弃
<input ref='k'/>
<!-- 废弃 -->
# createRef
创建一个能够通过 ref 属性附加到 React 元素的 ref,需要通过点current访问dom节点或组件实例。
ref 的值根据节点的类型而有所不同:
当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性(例如下面this.inputRef.current为input的dom节点)。
当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
不能在函数组件上使用 ref 属性,因为他们没有实例。
为 DOM 元素添加 ref:使用 ref 去存储 DOM 节点的引用
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.textInput.current.focus();
}
render() {
// 告诉 React 我们想把 <input> ref 关联到
// 构造器里创建的 `textInput` 上
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
# callback refs
“回调 refs”。它能更精细地控制何时 refs 被设置和解除。
不同于传递 createRef() 创建的 ref 属性,会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。
import React, { PureComponent } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
this.titleRef = null // 定义一个初始值
}
render() {
return (
<div>
<h1 ref={arg => this.titleRef = arg}>Hello,React</h1>
<button onClick={e => this.changeText()}>函数改变文本</button>
</div>
);
}
changeText() {
console.log(this.titleRef)
this.titleRef.innerHTML = "Hello"
}
}
export default App;
import React,{Component} from 'react'
export default class Cc extends Component{
constructor(arg) {
super()
this.state={inputvalue:'333'}
}
inputclick(){
console.log(this.doinput)//<input>这个dom元素
const value=this.doinput.value;
this.setState(()=>{
return{
inputvalue:value
}
})
}
render(){
return (<input
id="doinput"
className="input"
value={this.state.inputvalue}
onChange={this.inputclick.bind(this)}
ref={(input)=>{this.doinput=input}}
{/*把input这个元素传给this.doinput这个变量进行处理*/}
/>)
}
}
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// 使用原生 DOM API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// 组件挂载后,让文本框自动获得焦点
this.focusTextInput();
}
render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
可以在组件间传递回调形式的 refs,就像可以传递通过 React.createRef() 创建的对象 refs 一样。
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
在上面的例子中,Parent 把它的 refs 回调函数当作 inputRef props 传递给了 CustomTextInput,而且 CustomTextInput 把相同的函数作为特殊的 ref 属性传递给了 <input>。结果是,在 Parent 中的 this.inputElement 会被设置为与 CustomTextInput 中的 input 元素相对应的 DOM 节点。
TIP
如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用)。
# function useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue通常为null)。返回的 ref 对象在组件的整个生命周期内持续存在。
它指向一个 DOM 元素或 class 组件:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
# React.forwardRef
将子组件ref暴露出去,在父组件上绑定ref获取子组件内绑定的ref。
函数组件通过React.forwardRef创建后,才可以在该组件上绑定ref属性,并且ref属性绑定的值是该组件内的ref绑定的dom节点而非该组件的实例。在该组件内ref只是回调ref函数,并不能在组件内访问。
import React, { useEffect } from 'react';
const MyForwardedComponent = React.forwardRef((props, ref) => {
// 使用 ref 属性将 ref 绑定到 div 元素上
return <div ref={ref} tabIndex={0}>Forwarded Component</div>
})
let MyComponent = () => {
const myRef = React.createRef();
useEffect(() => {
// 在组件挂载后,通过 ref 访问 DOM 元素
const divElement = myRef.current;
// 手动设置焦点
divElement.focus();
// 获取 DOM 元素的大小,并在控制台中打印
const rect = divElement.getBoundingClientRect();
console.log('Div size:', rect);
}, []); // 空数组表示这个 effect 只在组件挂载和卸载时运行
return <MyForwardedComponent ref={myRef} />;
}
export default MyComponent
import React, { PureComponent, forwardRef } from "react";
const Test = forwardRef(function Profile(props, ref) {
React.useEffect(() => {
console.log(ref); //arg => this.titleRef = arg
}, []);
return (
<>
<h1>h1</h1>
<h2 ref={ref}>h2</h2>
</>
);
});
class App extends PureComponent {
constructor(props) {
super(props);
this.titleRef = null; // 定义一个初始值
}
render() {
return (
<div>
<Test ref={(arg) => (this.titleRef = arg)}></Test>
<button onClick={(e) => this.changeText()}>函数改变文本</button>
</div>
);
}
changeText() {
console.log(this.titleRef);
this.titleRef.innerHTML = "Hello wrld";
}
}
export default App;
# React.useImperativeHandle
搭配forwardRef一起使用,这是暴露在父组件的ref是useImperativeHandle函数第二个参数返回的对象。
import React, { PureComponent, forwardRef } from "react";
function FancyInput(props, ref) {
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
a(){
console.log(inputRef)
}
}));
return <input ref={inputRef }/>;
}
FancyInput = forwardRef(FancyInput);
class App extends PureComponent {
constructor(props) {
super(props);
this.fancyRef = null; // 定义一个初始值
}
render() {
return (
<>
<FancyInput ref={(arg) => (this.fancyRef = arg)} />
<div onClick={()=>{this.changeText()}}>click</div>
</>
);
}
changeText() {
console.log(this.fancyRef); //{focus: ƒ, a: ƒ}
this.fancyRef.focus() //input聚焦
}
}
export default App;
# react生命周期函数
# constructor
在 React 中,构造函数仅用于以下两种情况:
- 通过给 this.state 赋值对象来
初始化内部 state。 - 为事件处理函数绑定实例
constructor(props) {
super(props);
// 以下写法将props中的值写入state毫无必要(可以直接用 this.props.color)
//同时还产生bug(更新 prop 中的 color 时,并不会影响 state)。
this.state = { color: props.color };
}
生命周期函数指的是在某一个时刻react组件会自动调用执行的函数
componentWillMount==>组件即将被挂载到页面的时刻自动执行[react17移除]- componentDidMount==>组件被挂载到页面的之后自动执行
shouldComponentUpdate==>组件被更新之前,自动执行- 需要返回一个布尔值(可以做拦截器)
- 这是个提升性能优化的非常好的钩子函数,可以人为告诉某些地方不需要重新无效渲染,避免无效diff比对
componentWillUpdate===>组件被更新之前且shouldComponentUpdate返回值为true才会执行[react17移除]- componentDidUpdate===>组件更新完成之后执行
componentWillReceiveProps[react17移除]- 当一个组件要从父组件接受参数
- 只要父组件的render函数被重新执行,子组件的这个生命周期就会被执行
如果这个组件第一次存在父组件之中,不会执行- 如果之前已经存在,才会执行
- componentWillUnmount
- render===》渲染
- getSnapshotBeforeUpdate()=>在最近一次渲染输出(提交到 DOM 节点)之前调用。
- getDefaultProps:只适用React.createClass创建的类,其他想要使用可以用静态属性定义defaultProps代替
var ShowTitle = React.createClass({
getDefaultProps:function(){
return{
title : "百度科技"
}
},
render : function(){
return <h1>{this.props.title}</h1>
}
});
//静态属性非生命周期
static defaultProps={
title : "百度科技"
}
render(){
return(
<Fragment>
<div>{this.props.title}---</div>
</Fragment>
)
}
- getInitialState(es6中不使用):初始化state,es6可以在constructor中实现
# react生命周期图[老版本]
# 生命周期使用场景
- 子组件如果没有发生变化,但是父组件render改变,也会触发子组件render变化,可以使用
shouldComponentUpdate进行拦截处理;
此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。应该考虑使用内置的 PureComponent 组件 。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
如果一定要手动编写此函数,可以将 this.props 与 nextProps 以及 this.state 与nextState 进行比较,并返回 false 以告知 React 可以跳过更新。请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。
//性能优化
shouldComponentUpdate(nextProps,nextState){
if(nextProps.content!==this.props.content){
return true
}else{
return false
}
}
# shouldComponentUpdate getDerivedStateFromProps 案例
import React,{Component} from 'react'
import C1 from './C1.js'
export default class P1 extends Component{
constructor(props){
super(props)
this.state= {k:1}
}
render(){
return (<div>
<div>{this.state.k}</div>
<button onClick={this.add}>+++</button>
<C1 {...this.state}/>
</div>)
}
add = () => {
this.setState({
k:this.state.k+1
})
}
shouldComponentUpdate(nextProps,nextState){
console.log('shouldcomponentupdate')
// 如果父组件返回false,子组件的props.k就不会变化,不会重新渲染
return true
}
static getDerivedStateFromProps(nextProps, prevState){
console.log('getDerivedStateFromProps')
// 这里虽然返回null,但是add函数修改了state,子组件依旧能获取新的props
return null
}
}
import React,{Component} from 'react'
export default class C1 extends Component{
constructor(props){
super(props)
// console.log(this.props)
this.state={foo:0}
}
render(){
return (<div>
<div>{this.props.k}</div>
<br/>
<div>{this.state.foo}</div>
</div>)
}
static getDerivedStateFromProps(nextProps, prevState){
// 当nextprops大于20时,this.state.foo就不再继续更新
// this.props.k会继续更新
console.log('child getDerived')
if(nextProps.k>20){
return null
}
return {foo:nextProps.k}
}
}
react和vue父子组件更新的比较
- vue 父子组件如果没有数据依赖,都只更新自己的数据,也就是beforeUpdate/updated只会本组件的会使用,如果父组件传递给子组件props而子组件并没有使用,也不会触发!(找时间验证下这句话)
- 父组件更改,子组件会重新render,可使用此函数避免无效渲染,同时
子组件render,不会触发父组件的render重新渲染;
componentDidMount
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。
如需通过网络请求获取数据,此处最佳。
这个方法是比较适合添加订阅的地方。如果添加了订阅,一定在 componentWillUnmount() 里取消订阅
可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。 如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。谨慎使用该模式,它会导致性能问题。 通常,应在 constructor()中初始化state。
componentDidUpdate
componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
componentDidUpdate(prevProps, prevState, snapshot)
当组件更新后,可以在此处对 DOM 进行操作。如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。 (例如,当 props 未发生变化时,则不会执行网络请求)。
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,
正如上述的例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。
如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用), 则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()。
import React,{Component} from 'react';
class App extends Component{
constructor(props) {
super(props);
this.state={
name:"zhangsan",
age:31
}
}
render(){
console.log(111)
return(
<div>{this.state.name}</div>
)
}
componentDidMount(){
console.log(222);
let _this = this;
let num=0;
setInterval(()=>{
this.setState(()=>{
return this.state.age++
},()=>{
if(this.state.age%2){
this.setState(()=>{
return{
name:`wangqi${this.state.age}`
}
})
}
})
},1000)
}
componentDidUpdate(pstate,pprops,p3){
console.log(pstate)
console.log(pprops)
console.log(p3)
console.log(333)
}
getSnapshotBeforeUpdate(prevProps, prevState){
console.log(444)
return "1"
}
shouldComponentUpdate(nextProps, nextState){
console.log(555)
console.log(nextProps, nextState)
console.log(nextState.age)
if(nextState.age%5){
return true
}
return false
}
}
export default App;
+ 首次会执行render,然后执行componentDidMount(仅首次)
+ 当需要更新前,执行shouldComponentUpdate,返回false,不更新,返回true,则会继续执行render,
+ 然后可选getSnapshotBeforeUpdate,再到componentDidUpdate
componentWillUnmount
componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
警告
- React16新的生命周期弃用了 componentWillMount、componentWillReceivePorps,componentWillUpdate
- 新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate 来代替弃用的三个钩子函数
- React16并没有删除这三个钩子函数,但是不能和新增的钩子函数,React17将会删除
- 新增了对错误的处理(componentDidCatch)
组件生命周期的三个阶段
- Mounting(加载阶段)
- Updating(更新阶段)
- Unmounting(卸载阶段)
# 新的生命周期
Mounting(加载阶段:涉及4个钩子函数)
- constructor() 加载的时候调用一次,可以初始化state
- static getDerivedStateFromProps(props, state) 组件每次被rerender的时候,包括在组件构建之后(虚拟dom之后,实际dom挂载之前),每次获取新的props或state之后;每次接收 新的props之后都会返回一个对象作为新的state , 返回null则说明不需要更新state ;配合componentDidUpdate,可以覆盖
componentWillReceiveProps的所有用法
static getDerivedStateFromProps(nextProps, prevState){ //没有this对象 //nextprops:拿到最新的props,和老的satte console.log(nextProps, prevState) prevState ={k:1000} //去改变最新的state值 return {x:aa} }- render() react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行
- componentDidMount() 组件渲染之后调用,只调用一次
Updating(更新阶段:涉及5个钩子函数)
- static getDerivedStateFromProps(props, state)
- shouldComponentUpdate(nextProps, nextState) 组件接收到新的props或者state时调用,return true就会更新dom(使用diff算法更新),return false能阻止更新(不调用render)
- render()
- getSnapshotBeforeUpdate(prevProps, prevState) 触发时间: update发生的时候,在render之后,在组件dom渲染之前;返回一个值,作为componentDidUpdate的第三个参数;配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法
- componentDidUpdate() 组件加载时不调用,组件更新完成后调用
Unmounting(卸载阶段:涉及1个钩子函数) 组件渲染之后调用,只调用一次
Error Handling(错误处理)
- componentDidCatch(error,info) 任何一处的javascript报错会触发
- static getDerivedStateFromError():会在后代组件抛出错误后被调用
getSnapshotBeforeUpdate应用场景:通常和componentDidUpdate搭配!
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果有一个快照值,说明我们刚刚添加了新项目。
// 调整滚动位置,以便这些新项目不会将旧项目推出视图。
// (这里的快照是从getSnapshotBeforeUpdate返回的值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
# react中的动画 react-transition-group
- Transition
- CSSTransition
- SwitchTransition
- TransitionGroup
+ 想要首次也有动画效果,需要利用xx-appear
+ classNames="kk",那么类名都为kk-enter这种类型
+ 除了使用类名,也可以利用react-transition-group中钩子函数来进行处理动画
+ unmountOnExit:隐藏移除,显示展现,添加在<CSSTransition>上方便隐藏移除dom元素
+ in:如果是改变多个组件动画,那么外层需要包裹TransitionGroup,同时CSSTransition的in不需要了
npm i react-transition-group --save
npm i react-bootstrap --save
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { Container, Button, Alert } from 'react-bootstrap';
import { CSSTransition } from 'react-transition-group';
import './styles.css';
function Example() {
const [showButton, setShowButton] = useState(true);
const [showMessage, setShowMessage] = useState(false);
return (
<Container style={{ paddingTop: '2rem' }}>
{showButton && (
<Button
onClick={() => setShowMessage(true)}
size="lg"
>
Show Message
</Button>
)}
<CSSTransition
in={showMessage}
timeout={300}
classNames="alert"
unmountOnExit
onEnter={() => setShowButton(false)}
onExited={() => setShowButton(true)}
>
{/*
in:如果是改变多个则不需要,但是外层需要加TransitionGroup标签(showMessage:需要改变的值)
classNames:记住是classNames有s
unmountOnExit:隐藏移除,显示展现
CSSTransition:CSS三个字母都要大写!!!
onEnter:钩子函数
*/}
<Alert
variant="primary"
dismissible
onClose={() => setShowMessage(false)}
>
<Alert.Heading>
Animated alert message
</Alert.Heading>
<p>
This alert message is being transitioned in and
out of the DOM.
</p>
<Button onClick={() => setShowMessage(false)}>
Close
</Button>
</Alert>
</CSSTransition>
</Container>
);
}
ReactDOM.render(
<Example />,
document.getElementById('root')
);
.alert-enter {
opacity: 0;
transform: scale(0.9);
}
.alert-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.alert-exit {
opacity: 1;
}
.alert-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
+ onEnter
+ onEntering
+ onEntered
+ onExit
+ onExiting
+ onExited
# SwitchTransition可以完成两个组件之间切换的炫酷动画
比如有一个按钮需要在on和off之间切换,我们希望看到on先从左侧退出,off再从右侧进入
SwitchTransition中主要有一个属性mode,对应两个值:
- in-out:表示新组件先进入,旧组件再移除;
- out-in:表示就组件先移除,新组建再进入
- SwitchTransition组件里面要有CSSTransition,不能直接包裹你想要切换的组件
- 里面的CSSTransition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是key属性
import React from 'react'
import './test.css'
import { SwitchTransition, CSSTransition } from "react-transition-group";
export default class SwitchAnimation extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isOn: true
}
}
render() {
const {isOn} = this.state;
return (
<SwitchTransition mode="in-out">
<CSSTransition classNames="btn"
timeout={500}
key={isOn ? "on1" : "off1"}>
{
<button onClick={this.btnClick.bind(this)}>
{isOn ? "on1": "off1"}
</button>
}
</CSSTransition>
</SwitchTransition>
)
}
btnClick() {
this.setState({isOn: !this.state.isOn})
}
}
.btn-enter {
transform: translate(100%, 0);
opacity: 0;
}
.btn-enter-active {
transform: translate(0, 0);
opacity: 1;
transition: all 500ms;
}
.btn-exit {
transform: translate(0, 0);
opacity: 1;
}
.btn-exit-active {
transform: translate(-100%, 0);
opacity: 0;
transition: all 500ms;
}
# TransitionGroup
当有一组动画的时候,就可将这些CSSTransition放入到一个TransitionGroup中来完成动画
同样CSSTransition里面没有in属性,用到了key属性
TransitionGroup在感知children发生变化的时候,先保存移除的节点,当动画结束后才真正移除
其处理方式如下:
- 插入的节点,先渲染dom,然后再做动画
- 删除的节点,先做动画,然后再删除dom
import './test.css'
import React, { PureComponent } from 'react'
import { CSSTransition, TransitionGroup } from 'react-transition-group';
export default class GroupAnimation extends PureComponent {
constructor(props) {
super(props);
this.state = {
friends: []
}
}
render() {
return (
<div>
<TransitionGroup>
{
this.state.friends.map((item, index) => {
return (
<CSSTransition classNames="friend" timeout={300} key={index}>
<div>{item}</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={e => this.addFriend()}>+friend</button>
</div>
)
}
addFriend() {
this.setState({
friends: [...this.state.friends, "coderwhy"]
})
}
}
.friend-enter {
transform: translate(100%, 0);
opacity: 0;
}
.friend-enter-active {
transform: translate(0, 0);
opacity: 1;
transition: all 500ms;
}
.friend-exit {
transform: translate(0, 0);
opacity: 1;
}
.friend-exit-active {
transform: translate(-100%, 0);
opacity: 0;
transition: all 500ms;
}
# react传送门createPortal
import React from 'react'
import { createPortal } from 'react-dom'
class Dialog extends React.Component {
constructor() {
super(...arguments)
const doc = window.document
this.node = doc.createElement('div')
doc.body.appendChild(this.node)
window.document.body.appendChild(
window.document.createElement('div')
)
}
componentWillUnmount() {
window.document.body.removeChild(this.node)
}
render() {
return createPortal(
<div class='dialog'>
{this.props.children}
</div>,
this.node
)
}
}