React学习笔记(七)--React高级技巧

963 阅读25分钟

1. setState

setState更新状态其实有两种写法

setState这个方法在调用的时候是同步的,但是引起React的状态更新是异步的 【React状态更新是异步的】 为什么setState设计为异步的?

setState设计为异步,可以显著的提升性能:

  • 如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新;
  • 如果同步更新了state,但是还没有执行render函数,而且peops依赖于state中的数据,那么state和props不能保持同步;pstate和props不能保持一致性,会在开发中产生很多的问题;

1.1 对象式

setState(stateChange, [callback])

  • stateChange为状态改变对象 (该对象可以体现出状态的更改,是异步的)
  • callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用【异步更新后调用,可以拿到更新后的状态的值】

1.2 函数式

setState(updater, [callback])

  • updater为返回stateChange对象的函数【返回值是对象】
  • updater可以接收到state和props
  • callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用【和对象式是一样的】

1.3 代码

异步更新

函数式组件

import React, { Component } from 'react'

export default class Demo extends Component {

state = { count: 0 }

add = () => {
    //对象式的setState
    /* //1.获取原来的count值
    const {count} = this.state
    //2.更新状态
     // 方式一: 获取异步更新后的数据
    this.setState({count:count+1},()=>{
            console.log(this.state.count); //1,异步的回调,能拿到setState更新后的值
    })
    //console.log('12行的输出',this.state.count); //0 
    setState对象状态的改变是异步的,先执行完代码,react才会帮你更改状态,不能拿到setState更新后的值
    */

    //函数式的setState
    this.setState((state, props) => { return { count: state.count + 1 } })
}
  
render() {
        return (
                <div>
                        <h1>当前求和为:{this.state.count}</h1>
                        <button onClick={this.add}>点我+1</button>
                </div>
        )
   }
}

类式组件

import React, { Component } from 'react';

function Home(props) {
  // Hello World
  return <h1>{props.message}</h1>
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      message: "Hello World"
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.message}</h2>
        <button onClick={e => this.changeText()}>改变文本</button>
        <Home message={this.state.message}/>
      </div>
    )
  }

  componentDidUpdate() {
    // 方式二: 获取异步更新的state
    console.log(this.state.message);
  }

  changeText() {
    // 2.setState是异步更新
    // this.setState({
    //   message: "你好啊,李银河"
    // })
    // console.log(this.state.message); // Hello World

    // 方式一: 获取异步更新后的数据
    // setState(更新的state, 回调函数)
    this.setState({
      message: "你好啊,李银河"
    }, () => {
      console.log(this.state.message);
    })
  }
}

同步更新

setState在正常状态下是异步更新的,但在如下情况下是同步更新的:

   // 情况一: 将setState放入到定时器中
    setTimeout(() => {
      this.setState({
        message: "你好啊,李银河"
      })
      console.log(this.state.message);
    }, 0);
    
    // 情况二: 原生dom事件中
    componentDidMount() {
    document.getElementById("btn").addEventListener("click", (e) => {
      this.setState({
        message: "你好啊,李银河"
      })
      console.log(this.state.message);
    })
  }

1.4 setState数据的合并与本身的合并

我通过setState去修改message,是不会对name产生影响的;源码中其实是有对原对象和新对象进行合并的:

image.png

数据合并代码

通过Object.assign方法,将后两个复制到目标对象,其中第二个参数中如果存在和第三个参数相同的属性,则将其替换成第三个参数的内容

import React, { Component } from 'react';
export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      message: "Hello World",
      name: "coderwhy"
    }
  }

  render() {
    return (
      <div>
        <h2>{this.state.message}</h2>
        <h2>{this.state.name}</h2>
        <button onClick={e => this.changeText()}>改变文本</button>
      </div>
    )
  }

  changeText() {
    // 了解真相你才能获得真正的自由
    this.setState({
      message: "你好啊,李银河"
    });

    // Object.assign({}, this.state, {message: "你好啊,李银河"})
  }
}

本身合并代码

import React, { Component } from 'react'

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
      </div>
    )
  }

  increment() {
    // 1.setState本身被合并,不论调用几次都只能加1
    // this.setState({
    //   counter: this.state.counter + 1
    // });
    // this.setState({
    //   counter: this.state.counter + 1
    // });
    // this.setState({
    //   counter: this.state.counter + 1
    // });

    // 2.setState合并时进行累加
    // this.setState((上一次的state, props)
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
  }
}

1.5 state的引用数据尽量不要直接修改

friends是数组,是引用类型,存储的数据为一个指向堆中内存的指针,
push一个对象是将一个对象的引用加入到原数组在堆中的内存,之后
setState{  friends: this.state.friends } 是将原指针赋值给friends,使用
PureComponent(shouldComponentUpdate会比较新旧state)会出现问题,
使用Component没有上述问题,但是也不推荐直接修改
import React, { PureComponent } from "react";

export default class App extends PureComponent {
  // 引用类型
  state = {
    friends: [
      { name: "lilei", age: 20 },
      { name: "lily", age: 25 },
      { name: "lucy", age: 22 },
    ],
  };
  // shouldComponentUpdate(newProps, newState) {
  //   if (newState.friends !== this.state.friends) {
  //     return true;
  //   }

  //   return false;
  // }

  render() {
    return (
      <div>
        <h2>好友列表:</h2>
        <ul>
          {this.state.friends.map((item, index) => {
            return (
              <li key={item.name}>
                姓名: {item.name}
                年龄: {item.age}
                <button onClick={() => this.incrementAge(index)}>age+1</button>
              </li>
            );
          })}
        </ul>
        <button onClick={() => this.insertData()}>添加数据</button>
      </div>
    );
  }

  insertData() {
    // 1.在开发中不要这样来做
    // const newData = {name: "tom", age: 30}
    // this.state.friends.push(newData);
    // this.setState({
    //   friends: this.state.friends
    // });
   

    // 2.推荐做法
    const newFriends = [...this.state.friends];
    newFriends.push({ name: "tom", age: 30 });
    this.setState({
      friends: newFriends,
    });
  }

  incrementAge(index) {
    const newFriends = [...this.state.friends];
    newFriends[index].age += 1;
    this.setState({
      friends: newFriends,
    });
  }
}

1.6 总结

对象式的setState是函数式的setState的简写方式【语法糖】 使用原则

  • 如果新状态不依赖于原状态【使用对象方式】
  • 如果新状态依赖于原状态 【使用函数方式】
  • 如果需要在setState()执行后,获取最新的状态数据,可以在第二个callback函数中读取到异步更新的最新值
  • 在组件生命周期或React合成事件中,setState是异步;
  • 在setTimeout或者原生dom事件中,setState是同步;
  • 不要直接修改state中的引用数据

2. lazyLoad

路由组件的lazyLoad

//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))

//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
    <Route path="/xxx" component={Xxxx}/>
    <Redirect to="/login"/>
</Switch>
</Suspense>

案例

import React, { Component,lazy,Suspense} from 'react'
import {NavLink,Route} from 'react-router-dom'

// import Home from './Home'
// import About from './About'
//Loading组件用于懒加载未加载出来时展示的内容,不能也用懒加载,需要正常引入
import Loading from './Loading'
//懒加载引入Home,About组件,注意这里箭头函数不能用{}
const Home = lazy(()=> import('./Home') )
const About = lazy(()=> import('./About'))

export default class Demo 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">
                                {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
                                <NavLink className="list-group-item" to="/about">About</NavLink>
                                <NavLink className="list-group-item" to="/home">Home</NavLink>
                            </div>
                        </div>
                        <div className="col-xs-6">
                            <div className="panel">
                                <div className="panel-body">
                                    {通过<Suspense>包裹并返回一个自定义loading界面}
                                    <Suspense fallback={<Loading/>}>
                                            {/* 注册路由 */}
                                            <Route path="/about" component={About}/>
                                            <Route path="/home" component={Home}/>
                                    </Suspense>
                                </div>
                            </div>
                    </div>
                </div>
            </div>
		)
	}
}

3. Fragment

使用

<Fragment></Fragment>

//短语法
<></>
注意:Fragment只用拥有key属性,<></>什么属性都不能拥有

作用

可以不用必须有一个真实的DOM根标签了,用来代替最外层div

import React, { PureComponent, Fragment } from "react";

export default class App extends PureComponent {
  state = {
    counter: 0,
    friends: [
      { name: "why", age: 18 },
      { name: "lilei", age: 20 },
      { name: "kobe", age: 25 },
    ],
  };

  render() {
    return (
      <>
        {this.state.friends.map((item, index) => {
          return (
            <Fragment key={item.name}>
              <div>{item.name}</div>
              <p>{item.age}</p>
              <hr />
            </Fragment>
          );
        })}
      </>
    );
  }
}

4. StrictMode

//index.jsx 为所有组件开启严格模式检查
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
  • StrictMode 是一个用来突出显示应用程序中潜在问题的工具.
  • 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI.
  • 它为其后代元素触发额外的检查和警告.
  • 严格模式检查仅在开发模式下运行;它们不会影响生产构建.
  • 可以为应用程序的任何部分启用严格模式.
  • 如下图案例: 不会对 Header 和 Footer 组件运行严格模式检查; 但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查

1640675863(1).png

严格模式检查的是什么?

  1. 识别不安全的生命周期:
  2. 使用过时的ref API
  3. 使用废弃的findDOMNode方法 :

在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了

  1. 检查意外的副作用 :

严格模式下组件的constructor会被调用两次; 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用; 在生产环境中,是不会被调用两次的;

  1. 检测过时的context API :

早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的; 目前这种方式已经不推荐使用

5. Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

Context相关API

React.createContext :

  • 创建一个需要共享的Context对象:
  • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
  • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值 Context.Provider:
  • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
  • Provider 接收一个 value 属性,传递给消费组件;
  • 一个 Provider 可以和多个消费组件有对应关系;
  • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
  • 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染; Class.contextType
  • 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
  • 这能让你使用 this.context 来消费最近 Context 上的那个值; 你可以在任何生命周期中访问到它,包括 render 函数中; Context.Consumer
  • 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。
  • 这里需要 函数作为子元素(function as child)这种做法;
  • 这个函数接收当前的 context 值,返回一个 React 节点;

使用

1) 创建Context容器对象:
	const XxxContext = React.createContext()  
	
2) 渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
	<xxxContext.Provider value={数据}>
		子组件
       </xxxContext.Provider>
    
3) 后代组件读取数据:

//第一种方式:仅适用于类组件 
	  static contextType = xxxContext  // 声明接收context
	  this.context // 读取context中的value数据
	  
//第二种方式: 函数组件与类组件都可以,但要是使用多个嵌套的话只能用这个(函数,类)
	  <xxxContext.Consumer>
	    {
	      value => ( // value就是context中的value数据
	        要显示的内容
	      )
	    }
	  </xxxContext.Consumer>

注意

在应用开发中一般不用context, 一般都它的封装react插件

案例

import React, { Component } from 'react'
import './index.css'

//创建Context对象
const MyContext = React.createContext(默认值,当value无效时使用)
// 解构赋值,取出Provider和Consumer,简化后续写法
const { Provider, Consumer } = MyContext

export default class A extends Component {

	state = { username: 'tom', age: 18 }

	render() {
		const { username, age } = this.state
		return (
                <div className="parent">
                    <h3>我是A组件</h3>
                    <h4>我的用户名是:{username}</h4>
                    <Provider value={{ username: username, age: age }}>
                            <B />
                    </Provider>
                </div>
		)
	}
}
// 不需要写任何东西
class B extends Component {
	render() {
		return (
			<div className="child">
				<h3>我是B组件</h3>
				<C />
			</div>
		)
	}
}
// 类式组件
/* class C extends Component {
    //声明接收context
    static contextType = MyContext
    render() {
            const {username,age} = this.context
            return (
              <div className="grand">
                    <h3>我是C组件</h3>
                    <h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
              </div>
            )
    }
} */
// 函数式组件
function C() {
	return (
        <div className="grand">
            <h3>我是C组件</h3>
            <h4>我从A组件接收到的用户名:
                <Consumer>
                    {
                       value => { return `${value.username},年龄是${value.age}` }
                    }
                </Consumer>
            </h4>
        </div>
	)
}

多层嵌套

import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

const ThemeContext = React.createContext({
  color: "black"
})

function ProfileHeader() {
  return (
    <UserContext.Consumer>
      {
        value => {
          return (
            <ThemeContext.Consumer>
              {
                theme => {
                  return (
                    <div>
                      <h2 style={{color: theme.color}}>用户昵称: {value.nickname}</h2>
                      <h2>用户等级: {value.level}</h2>
                      <h2>颜色: {theme.color}</h2>
                    </div>
                  )
                }
              }
            </ThemeContext.Consumer>
          )
        }
      }
    </UserContext.Consumer>
  )
}

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
          <ThemeContext.Provider value={{ color: "red" }}>
            <Profile />
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    )
  }
}

6. 组件性能优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的生命周期钩子shouldComponentUpdate()总是返回true

React给我们提供了一个生命周期方法shouldComponentUpdate(很多时候,我们简称为SCU),这个方法接受参数,并且需要有返回值

该方法有两个参数:

  • 参数一:nextProps修改之后,最新的props属性
  • 参数二:nextState修改之后,最新的state属性 该方法返回值是一个boolean类型
  • 返回值为true,那么就需要调用render方法;
  • 返回值为false,那么就不需要调用render方法; 默认返回的是true,也就是只要state发生改变,就会调用render方法;

解决

办法1: 
    重写shouldComponentUpdate()方法
    比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

实例

import React, {Component} from 'react';

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0,
      message: "Hello World"
    }
  }

  render() {
    console.log("App render函数被调用");
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.changeText()}>改变文本</button>
      </div>
    )
  }

  shouldComponentUpdate(nextProps, nextState) {
  //如果counter更新,则重新调用render方法,其他情况不重新调用render
    if (this.state.counter !== nextState.counter) {
      return true;
    }
    
    return false;
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  changeText() {
    this.setState({
      message: "你好啊,李银河"
    })
  }
}

如果所有的类,我们都需要手动来实现shouldComponentUpdate,那么会给我们开发者增加非常多的工作量。 我们来设想一下shouldComponentUpdate中的各种判断的目的是什么?

props或者state中的数据是否发生了改变,来决定shouldComponentUpdate返回true或者false; 事实上React已经考虑到了这一点,所以React已经默认帮我们实现好了,如何实现呢?将class继承自PureComponent。

办法2:  
    使用PureComponent
    PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
    注意: 
        只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
        不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

实例

import React, { PureComponent } from 'react' //项目中一般使用PureComponent
import './index.css'

export default class Parent extends PureComponent {

	state = { carName: "奔驰c36", stus: ['小张', '小李', '小王'] }

	addStu = () => {
		/*错误写法,这样无效,因为还是同一个对象 
		const {stus} = this.state
		stus.unshift('小刘')
		this.setState({stus}) */
		// 正确写法
		const { stus } = this.state
		this.setState({ stus: ['小刘', ...stus] })
	}

	changeCar = () => {
		this.setState({ carName: '迈巴赫' })
	}
/*
shouldComponentUpdate(nextProps, nextState) {

    // console.log(this.props,this.state); //目前的props和state
    // console.log(nextProps,nextState); //接下要变化的目标props,目标state
    if (this.state.carName === nextState.carName) {
            return false
    } else {
            return true
    }

}
*/
    render() {
        console.log('Parent---render');
        const { carName } = this.state
        return (
            <div className="parent">
                <h3>我是Parent组件</h3>
                {this.state.stus}&nbsp;
                <span>我的车名字是:{carName}</span><br />
                <button onClick={this.changeCar}>点我换车</button>
                <button onClick={this.addStu}>添加一个小刘</button>
                <Child carName="奥拓" />
            </div>
        )
    }
}

class Child extends PureComponent {

/* shouldComponentUpdate(nextProps,nextState){
        console.log(this.props,this.state); //目前的props和state
        console.log(nextProps,nextState); //接下要变化的目标props,目标state
        return !(this.props.carName === nextProps.carName)
} */

	render() {
		console.log('Child---render');
		return (
			<div className="child">
				<h3>我是Child组件</h3>
				<span>我接到的车是:{this.props.carName}</span>
			</div>
		)
	}
}

但还是有一个问题PureComponent只能解决类式组件的性能优化问题,无法解决函数式组件的性能优化。函数式组件的性能优化要用高阶组件memo,详细用法可以看我的另一篇文章 React Hooks 全解

7. 用React实现插槽

React本身是没有插槽这个概念的,但可以React的灵活性可以让其实现类似插槽的功能 App.jsx

import React, { Component } from "react";

import NavBar from "./NavBar";
import NavBar2 from "./NavBar2";

export default class App extends Component {
  render() {
    const leftJsx = <span>aaa</span>;
    return (
      <div>
        <NavBar name="" title="" className="">
          <span>aaa</span>
          <strong>bbb</strong>
          <a href="/#">ccc</a>
        </NavBar>

        <NavBar2
          leftSlot={leftJsx}
          centerSlot={<strong>bbb</strong>}
          rightSlot={<a href="/#">ccc</a>}
        />
      </div>
    );
  }
}

方法一:NavBar.jsx

利用props.children属性实现插槽功能

import React, { Component } from "react";

export default class NavBar extends Component {
  render() {
    console.log(this.props.children[0]);

    return (
      <div className="nav-item nav-bar">
        <div className="nav-left">{this.props.children[0]}</div>

        <div className="nav-item nav-center">{this.props.children[1]}</div>

        <div className="nav-item nav-right">{this.props.children[2]}</div>
      </div>
    );
  }
}

方法二:NavBar2.jsx 利用props的属性实现插槽

import React, { Component } from "react";

export default class NavBar2 extends Component {
  render() {
    const { leftSlot, centerSlot, rightSlot } = this.props;

    return (
      <div className="nav-item nav-bar">
        <div className="nav-left">{leftSlot}</div>

        <div className="nav-item nav-center">{centerSlot}</div>

        <div className="nav-item nav-right">{rightSlot}</div>
      </div>
    );
  }
}

style.css

body {
  padding: 0;
  margin: 0;
}

.nav-bar {
  display: flex;
}

.nav-item {
  height: 44px;
  line-height: 44px;
  text-align: center;
}

.nav-left, .nav-right {
  width: 70px;
  background-color: red;
}

.nav-center {
  flex: 1;
  background-color: blue;
}

{466e9f55-1188-4ffb-b0e7-821a5392e6cf}.png

8. 错误边界

理解:

错误边界:用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

Parent

import React, { Component } from 'react'
import Child from './Child'

export default class Parent extends Component {

	state = {
		hasError:'' //用于标识子组件是否产生错误
	}

	//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
	static getDerivedStateFromError(error){
		console.log('@@@',error);
		return {hasError:error}
	}

	componentDidCatch(){
		console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
	}

	render() {
		return (
			<div>
				<h2>我是Parent组件</h2>
				{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <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:'jack',age:19},
			{id:'003',name:'peiqi',age:20},
		]
		// users:'abc'
	}

	render() {
		return (
			<div>
				<h2>我是Child组件</h2>
				{
					this.state.users.map((userObj)=>{
						return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>
					})
				}
			</div>
		)
	}
}

9. 组件通信方式总结

方式:

props:
    (1).children props
    (2).render props
消息订阅-发布:
    pubs-sub、event等等
集中式管理:
    redux、dva等等
conText:
    生产者-消费者模式

组件间的关系:

父子组件:props
    兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
    祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)

10. 高阶函数与高阶组件

1. 高阶函数

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

  1. 若A函数,接收的参数是一个或多个函数,那么A就可以称之为高阶函数。
  2. 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

常见的高阶函数有:Promise、setTimeout、arr.map()等等 可以参考下面这一篇文章:

高阶函数,关于AOP,偏函数,柯里化都有不错的记录,感觉还是不错的 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

  function sum(a){
                return(b)=>{
                        return (c)=>{
                                return a+b+c
                        }
                }
        }  
const result = sum(1)(2)(3)
console.log(result);

案例1

<script type="text/babel">
    //创建组件
class Login extends React.Component{

    //初始化状态
    state = {
            username:'', //用户名
            password:'' //密码
    }

    //保存用户名到状态中
    saveUsername = (event)=>{
            this.setState({username:event.target.value})
    }

    //保存密码到状态中
    savePassword = (event)=>{
            this.setState({password:event.target.value})
    }

    //表单提交的回调
    handleSubmit = (event)=>{
            event.preventDefault() //阻止表单提交
            const {username,password} = this.state
            alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
    }

    render(){
        return(
            <form onSubmit={this.handleSubmit}>
                用户名:<input onChange={this.saveUsername} type="text" name="username"/>
                密码:<input onChange={this.savePassword} type="password" name="password"/>
                <button>登录</button>
            </form>
        )
    }
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>

上方受控组件的写法太过繁琐,可以用函数柯里化简写

<!-- 准备好一个“容器” -->
<div id="test"></div>

<script type="text/babel">
  
    //创建组件
    class Login extends React.Component{
            //初始化状态
            state = {
                    username:'', //用户名
                    password:'' //密码
            }

            //保存表单数据到状态中
            saveFormData = (dataType)=>{
                    return (event)=>{
                            this.setState({[dataType]:event.target.value})
                    }
            }

            //表单提交的回调
            handleSubmit = (event)=>{
                    event.preventDefault() //阻止表单提交
                    const {username,password} = this.state
                    alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
            }
            render(){
                return(
                    <form onSubmit={this.handleSubmit}>
                           {/*事件中要求接收的是一个函数,即{}内是一个函数*/}
                        用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
                        密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
    }
    //渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
<!-- 准备好一个“容器” -->
<div id="test"></div>

<script type="text/babel">
//创建组件
class Login extends React.Component{
        //初始化状态
        state = {
                username:'', //用户名
                password:'' //密码
        }

        //保存表单数据到状态中
        saveFormData = (dataType,event)=>{
                this.setState({[dataType]:event.target.value})
        }

        //表单提交的回调
        handleSubmit = (event)=>{
                event.preventDefault() //阻止表单提交
                const {username,password} = this.state
                alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
        }
        render(){
                return(
                    <form onSubmit={this.handleSubmit}>
                          {/*事件中要求接收的是一个函数,即{}内是一个函数*/}
                        用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>
                        密码:<input onChange={event => this.saveFormData('password',event) } type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
        }
    }
    //渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'))
</script>

2. 高阶组件

高阶组件的英文是 Higher-Order Components,简称为 HOC;

官方的定义:高阶组件是参数为组件,返回值为新组件的函数;

  • 高阶组件 本身不是一个组件,而是一个函数;
  • 这个函数的参数是一个组件,返回值也是一个组件;
  • 高阶组件并不是React API的一部分,它是基于React的 组合特性而形成的设计模式;
  • 高阶组件在一些React第三方库中非常常见:
    1. 比如redux中的connect;
    2. 比如react-router中的withRouter;

高阶组件的定义

高阶组件的调用过程类似于这样

1640658369(1).png

高阶函数的编写过程类似于这样

1640658419(1).png

组件的名称问题

  • 在ES6中,类表达式中类名是可以省略的;
  • 组件的名称都可以通过displayName来修改;
//App.jsx

import React, { PureComponent } from "react";

//App组件想要使用props必须要从包裹他的高阶组件中得到
class App extends PureComponent {
  render() {
    return <div>App: {this.props.name}</div>;
  }
}

// 类式组件
//function enhanceComponent(形参)

function enhanceComponent(WrappedComponent) {
  class NewComponent extends PureComponent {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
  // 重新定义组件类名
  NewComponent.displayName = "Kobe";
  return NewComponent;
}

// 函数式组件
function enhanceComponent2(WrappedComponent) {
  function NewComponent(props) {
    return <WrappedComponent {...props} />;
  }
  // 重新定义函数组件名
  NewComponent.displayName = "Kobe";
  return NewComponent;
}

const EnhanceComponent = enhanceComponent2(App);

export default EnhanceComponent;

//index.js
//注意这里导出的App实际为EnhanceComponent,所以传递name属性传递给的是EnhanceComponent组件
import App from "./App.jsx";
ReactDOM.render(<App name="why" />, document.getElementById("root"));

应用一 – props的增强

为组件统一添加相同地区属性--不修改原有代码的情况下,添加新的props

import React, { PureComponent } from 'react';

// 定义一个高阶组件
function enhanceRegionProps(WrappedComponent) {
  return props => {
    return <WrappedComponent {...props} region="中国"/>
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <EnhanceHome nickname="coderwhy" level={90}/>
        <EnhanceAbout nickname="kobe" level={99}/>
      </div>
    )
  }
}

export default App;

案例--利用高阶组件来共享Context

原版:

import React, { PureComponent, createContext } from "react";

// 创建Context
const UserContext = createContext({
  nickname: "默认",
  level: -1,
  区域: "中国",
});

class Home extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {(user) => {
          return (
            <h2>
              Home:
              {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}
            </h2>
          );
        }}
      </UserContext.Consumer>
    );
  }
}

class About extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {(user) => {
          return (
            <h2>
              About:
              {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}
            </h2>
          );
        }}
      </UserContext.Consumer>
    );
  }
}

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <UserContext.Provider
          value={{ nickname: "why", level: 90, region: "中国" }}
        >
          <Home />
          <About />
        </UserContext.Provider>
      </div>
    );
  }
}

export default App;

高阶组件版

import React, { PureComponent, createContext } from "react";


class Home extends PureComponent {
  render() {
    return (
      <h2>
        Home:
        {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region} 标识:${this.props.id}`}
      </h2>
    );
  }
}

class About extends PureComponent {
  render() {
    return (
      <h2>
        About:
        {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region} 标识:${this.props.id}`}
      </h2>
    );
  }
}

class Detail extends PureComponent {
  render() {
    return (
      <ul>
        <li>{this.props.nickname}</li>
        <li>{this.props.level}</li>
        <li>{this.props.region}</li>
        <li>{this.props.id}</li>
      </ul>
    );
  }
}

// 定义一个高阶组件
function withUser(WrappedComponent) {
  return (props) => {
    return (
      <UserContext.Consumer>
        {(user) => {
          console.log(props, user);
          return <WrappedComponent {...props} {...user} />;
        }}
      </UserContext.Consumer>
    );
  };
}

const UserHome = withUser(Home);
const UserAbout = withUser(About);
const UserDetail = withUser(Detail);

// 创建Context
const UserContext = createContext({
  nickname: "默认",
  level: -1,
  区域: "中国",
});

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <UserContext.Provider
          value={{ nickname: "why", level: 90, region: "中国" }}
        >
          <UserHome id={1} />
          <UserAbout id={2} />
          <UserDetail id={3} />
        </UserContext.Provider>
      </div>
    );
  }
}

export default App;

应用二 – 渲染判断鉴权

在开发中,我们可能遇到这样的场景: 某些页面是必须用户登录成功才能进行进入; 如果用户没有登录成功,那么直接跳转到登录页面; 这个时候,我们就可以使用高阶组件来完成鉴权操作.

import React, { PureComponent } from 'react';

// 登录组件
class LoginPage extends PureComponent {
  render() {
    return <h2>LoginPage</h2>
  }
}
// 购物车组件
class CartPage extends PureComponent {
  render() {
    return <h2>CartPage</h2>
  }
}
// 高阶组件
function withAuth(WrappedComponent) {
  const NewCpn = (props) => {
    const {isLogin} = props;
    if (isLogin) {
      return <WrappedComponent {...props}/>
    } else {
      return <LoginPage/>
    }
  }

  NewCpn.displayName = "AuthCpn"

  return NewCpn;
}

const AuthCartPage = withAuth(CartPage);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <AuthCartPage isLogin={true}/>
      </div>
    )
  }
}

应用三 – 生命周期劫持

我们也可以利用高阶函数来劫持生命周期,在生命周期中完成自己的逻辑:

默认版

import React, { PureComponent } from 'react';

class Home extends PureComponent {

  // 即将渲染获取一个时间 beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // 渲染完成再获取一个时间 endTime
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`Home渲染时间: ${interval}`)
  }

  render() {
    return <h2>Home</h2>
  }
}


class About extends PureComponent {
  // 即将渲染获取一个时间 beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // 渲染完成再获取一个时间 endTime
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`About渲染时间: ${interval}`)
  }

  render() {
    return <h2>About</h2>
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home />
        <About />
      </div>
    )
  }
}

高阶组件版

import React, { PureComponent } from 'react';

//高阶组件
function withRenderTime(WrappedComponent) {
  return class extends PureComponent {
    // 即将渲染获取一个时间 beginTime
    UNSAFE_componentWillMount() {
      this.beginTime = Date.now();
    }

    // 渲染完成再获取一个时间 endTime
    componentDidMount() {
      this.endTime = Date.now();
      const interval = this.endTime - this.beginTime;
      console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
    }

    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About</h2>
  }
}

const TimeHome = withRenderTime(Home);
const TimeAbout = withRenderTime(About);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <TimeHome />
        <TimeAbout />
      </div>
    )
  }
}

高阶函数的意义

我们会发现利用高阶组件可以针对某些React代码进行更加优雅的处理.
其实早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用:

  • Mixin 可能会相互依赖,相互耦合,不利于代码维护
  • 不同的Mixin中的方法可能会相互冲突
  • Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性 当然,HOC也有自己的一些缺陷: HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难; HOC可以劫持props,在不遵守约定的情况下也可能造成冲突; Hooks的出现,是开创性的,它解决了很多React之前的存在的问题 比如this指向问题、比如hoc的嵌套复杂度问题等等

11. ref的转发--forwardRef

在前面我们学习ref时讲过,ref不能应用于函数式组件: 因为函数式组件没有实例,所以不能获取到对应的组件对象 . 但是,在开发中我们可能想要获取函数式组件中某个元素的DOM,这个时候我们应该如何操作呢?

  • 方式一:直接传入ref属性(错误的做法)
  • 方式二:通过forwardRef高阶函数;
import React, { PureComponent, createRef, forwardRef } from "react";

class Home extends PureComponent {
  render() {
    return <h2>Home</h2>;
  }
}

// function Profile(props) {
//   return <h2>Profile</h2>;
// }

// 高阶组件forwardRef
const Profile = forwardRef(function (props, ref) {
  return <p ref={ref}>Profile</p>;
});

export default class App extends PureComponent {
  titleRef = createRef();
  homeRef = createRef();
  profileRef = createRef();

  render() {
    return (
      <div>
        <h2 ref={this.titleRef}>Hello World</h2>

        <Home ref={this.homeRef} />

        <Profile ref={this.profileRef} name={"why"} />

        <button onClick={(e) => this.printRef()}>打印ref</button>
      </div>
    );
  }

  printRef() {
    console.log(this.titleRef.current);
    console.log(this.homeRef.current);
    console.log(this.profileRef.current);
  }
}

12. Portals的使用

某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM元 素上的).

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:

  • 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment;
  • 第二个参数(container)是一个 DOM 元素;

1640673275(1).png 通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点.
然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的.

1640673369(1).png

比如说,我们准备开发一个Modal组件,它可以将它的子组件渲染到屏幕的中间位置:

  • 步骤一:修改index.html添加新的节点
  • 步骤二:编写这个节点的样式
  • 步骤三:编写组件代码
//App.jsx
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';

class Modal extends PureComponent {
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      document.getElementById("modal")
    )
  }
}

class Home extends PureComponent {
  render() {
    return (
      <>
        <h2>Home</h2>
        <Modal>
          <h2>Title</h2>
        </Modal>
      </>
    )
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home/>
      </div>
    )
  }
}

//index.html
 <div id="root"></div>
 <div id="modal"></div>
 
//index.css
 #modal {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

//index.js
import App from "./App.jsx";
import "./css/index.css";

1640675023(1).png

13. React过渡动画

1. react-transition-group介绍

  • 在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验。
  • 当然,我们可以通过原生的CSS来实现这些过渡动画,但是React社区为我们提供了react-transition-group用来完成过渡动画。
  • React曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护,形成了现在的 react-transition-group。
  • react-transition-group本身非常小,不会为我们应用程序增加过多的负担。
  • 这个库可以帮助我们方便的实现组件的 入场 和 离场 动画,使用时需要进行额外的安装:
  • 官网:reactcommunity.org/react-trans…
npm npm install react-transition-group --save 

yarn yarn add react-transition-group

2. react-transition-group主要组件

react-transition-group主要包含四个组件

  1. Transition
    • 该组件是一个和平台无关的组件(不一定要结合CSS);
    • 在前端开发中,我们一般是结合CSS来完成样式,所以比较常用的是CSSTransition;
  2. CSSTransition
    • 在前端开发中,通常使用CSSTransition来完成过渡动画效果
  3. SwitchTransition
    • 两个组件显示和隐藏切换时,使用该组件
  4. TransitionGroup
    • 将多个动画组件包裹在其中,一般用于列表中元素的动画

3. CSSTransition

  • CSSTransition是基于Transition组件构建的:
  • CSSTransition执行过程中,有三个状态:appear、enter、exit;
  • 它们有三种状态,需要定义对应的CSS样式:
    • 第一类,开始状态:对于的类是-appear、-enter、exit;
    • 第二类:执行动画:对应的类是-appear-active、-enter-active、-exit-active;
    • 第三类:执行结束:对应的类是-appear-done、-enter-done、-exit-done;
  • CSSTransition常见对应的属性:
    • in:触发进入或者退出状态
      • 如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉;
      • 当in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class, 并且添加-enter-done的class;
      • 当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并 且添加-exit-done的class; CSSTransition常见属性
  • classNames:动画class的名称 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done;
  • timeout: 过渡动画的时间,到达设置时间后才会执行 onEnteredonExited
  • appear: 是否在初次进入添加动画(需要和in同时为true,然后再css中添加类xxx-appear设置初次进入动画)
  • unmountOnExit:退出后卸载组件--为true则在退出时将dom元素卸载掉
  • 其他属性可以参考官网来学习: reactcommunity.org/react-trans…
  • CSSTransition对应的钩子函数:主要为了检测动画的执行过程,来完成一些JavaScript的操作
    • onEnter:在进入动画之前被触发;

    • onEntering:在应用进入动画时被触发;

    • onEntered:在应用进入动画结束后被触发

    • onExit:在退出动画之前被触发;

    • onExiting:在应用退出动画时被触发;

    • onExited:在应用退出动画结束后被触发

//CSSTransition.css

/* 进入开始/初次进入开始 */
.card-enter, .card-appear {
  opacity: 0;
  transform: scale(.6);
}
/* 进入过程中/初次进入过程中 */
.card-enter-active, .card-appear-active {
  opacity: 1;
  transform: scale(1);
  transition: opacity 300ms, transform 300ms;
}
/* 进入完成/初次进入完成 */
.card-enter-done, .card-appear-done {
  opacity: 1;
}
/* 退出开始 */
.card-exit {
  opacity: 1;
  transform: scale(1);
}
/* 退出过程中 */
.card-exit-active {
  opacity: 0;
  transform: scale(.6);
  transition: all 300ms;
}
/* 退出完成 */
.card-exit-done {
  opacity: 0;
}

//CSSTransition.js
import React, { PureComponent } from "react";

import { CSSTransition } from "react-transition-group";

import { Card, Avatar } from "antd";
import {
  EditOutlined,
  EllipsisOutlined,
  SettingOutlined,
} from "@ant-design/icons";

const { Meta } = Card;

export default class CSSTransitionDemo extends PureComponent {
  state = {
    isShow: true,
  };

  render() {
    const { isShow } = this.state;

    return (
      <div>
        <button
          onClick={(e) => {
            this.setState({ isShow: !isShow });
          }}
        >
          显示/隐藏
        </button>
        <CSSTransition
          in={isShow}
          classNames="card"
          timeout={300}
          unmountOnExit={true}
          appear
          onEnter={(el) => console.log("开始进入")}
          onEntering={(el) => console.log("正在进入")}
          onEntered={(el) => console.log("进入完成")}
          onExit={(el) => console.log("开始退出")}
          onExiting={(el) => console.log("退出状态")}
          onExited={(el) => console.log("退出完成")}
        >
          <Card
            style={{ width: 300 }}
            cover={
              <img
                alt="example"
                src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png"
              />
            }
            actions={[
              <SettingOutlined key="setting" />,
              <EditOutlined key="edit" />,
              <EllipsisOutlined key="ellipsis" />,
            ]}
          >
            <Meta
              avatar={
                <Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
              }
              title="Card title"
              description="This is the description"
            />
          </Card>
        </CSSTransition>
      </div>
    );
  }
}

//App.js
import React, { PureComponent } from "react";
import CSSTransitionDemo from "./transition/CSSTransitionDemo";
export default class App extends PureComponent {
  render() {
    return (
      <div style={{ textAlign: "center", padding: "30px" }}>
        <CSSTransitionDemo />
      </div>
    );
  }
}

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import './transition/CSSTransition.css';
import 'antd/dist/antd.css';

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

1640771269(1).png

4. SwitchTransition

  1. SwitchTransition可以完成两个组件之间切换的炫酷动画:
    • 比如我们有一个按钮需要在on和off之间切换,我们希望看到on先从左侧退出,off再从右侧进入;
    • 这个动画在vue中被称之为 vue transition modes;
    • react-transition-group中使用SwitchTransition来实现该动画;
  2. SwitchTransition中主要有一个属性:mode,有两个值
    • in-out:表示新组件先进入,旧组件再移除;
    • out-in:表示就组件先移除,新组建再进入;
  3. 如何使用SwitchTransition呢?
    • SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹你想要切换的组件;
    • SwitchTransition里面的CSSTransition或Transition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是 key属性;
//SwitchTransition.css

.btn-enter {
  opacity: 0;
  transform: translateX(100%);
}

.btn-enter-active {
  opacity: 1;
  transform: translateX(0);
  transition: opacity 1000ms, transform 1000ms;
}

.btn-exit {
  opacity: 1;
  transform: translateX(0);
}

.btn-exit-active {
  opacity: 0;
  transform: translateX(-100%);
  transition: all 1000ms;
}



//SwitchTransition.js

import React, { PureComponent } from "react";

import "./SwitchTransition.css";
import { SwitchTransition, CSSTransition } from "react-transition-group";

export default class SwitchTransitionDemo extends PureComponent {
  state = {
    isOn: true,
  };

  render() {
    const { isOn } = this.state;

    return (
      <div>
        <SwitchTransition mode="out-in">
          <CSSTransition
            key={isOn ? "on" : "off"}
            classNames="btn"
            timeout={1000}
          >
            <button onClick={(e) => this.setState({ isOn: !this.state.isOn })}>
              {isOn ? "on" : "off"}
            </button>
          </CSSTransition>
        </SwitchTransition>
      </div>
    );
  }
}

5. TransitionGroup

当我们有一组动画时,需要将这些CSSTransition放入到一个TransitionGroup中来完成动画:

1640765811(1).png

//TransitionGroup.css

.item-enter, .item-appear {
  opacity: 0;
  transform: scale(.6);
}

.item-enter-active ,.item-appear-active{
  opacity: 1;
  transform: scale(1);
  transition: opacity 300ms, transform 300ms;
}

.item-enter-done ,.item-appear-done {
  color: rgb(255, 136, 0);
}

.item-exit {
  opacity: 1;
  transform: scale(1);
}

.item-exit-active {
  opacity: 0;
  transform: scale(.6);
  transition: opacity 300ms, transform 300ms;
}

.item-exit-done {
  opacity: 0;
}

//TransitionGroup.js

import React, { PureComponent } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";

import "./TransitionGroup.css";

export default class TransitionGroupDemo extends PureComponent {
  state = {
    names: ["coderwhy", "kobe", "lilei"],
  };

  render() {
    return (
      <div>
        <TransitionGroup>
          {this.state.names.map((item, index) => {
            return (
              <CSSTransition key={index} timeout={500} classNames="item" appear>
                <div>
                  {item}
                  <button onClick={(e) => this.removeItem(index)}>-</button>
                </div>
              </CSSTransition>
            );
          })}
        </TransitionGroup>
        <button onClick={(e) => this.addName()}>+name</button>
      </div>
    );
  }

  addName() {
    this.setState({
      names: [...this.state.names, "zgc"],
    });
  }

  removeItem(index) {
    // index indey indez
    this.setState({
      names: this.state.names.filter((item, indey) => index !== indey),
    });
  }
}

1640779071(1).png