第七节:高阶组件详解 和 动画详解

一. 高阶组件

1. 回顾

 什么是高阶函数?

 至少需要满足以下条件之一:(1). 接收一个或多个函数作为参数输入; (2). 输出一个函数。

 比如:filter、some、map、every、reduce 等都是高阶函数

 

2. 高阶组件定义

(1). 定义

  高阶组件是一个函数,这个函数的参数是一个组返回值也是一个组件 (高阶组件本身不是组件,而是一个函数)

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

  高阶组件并不是React API的一部分,它是基于React的组合特性而形成的设计模式, 比如redux中的connect、 比如react-router中的withRouter

(2).组件的名称问题:

  在ES6中,类表达式中类名是可以省略的;

  组件的名称都可以通过displayName来修改;

查看代码
/**
 * 定义一个高阶函数1
 * @param {Component} Cpn 一个组件
 * @returns  返回处理后的一个组件
 */

function Hoc1(Cpn) {
	class NewCpn extends PureComponent {
		render() {
			return <Cpn name="ypf"></Cpn>;
		}
	}
	NewCpn.displayName = "ypf2"; //可以自定组件名称
	return NewCpn;
}
/**
 * 定义一个高阶函数2
 * @param {Component} Cpn 一个组件
 * @returns  返回处理后的一个组件
 */
function Hoc2(Cpn) {
	// 在ES6中,类表达式中类名是可以省略的
	return class extends PureComponent {
		render() {
			return <Cpn name="ypf"></Cpn>;
		}
	};
}

class HelloWorld extends PureComponent {
	render() {
		return <h2>hello ypf</h2>;
	}
}
// 调用高阶组件
const MyHelloWorldFun = Hoc1(HelloWorld);
const MyHelloWorldFun2 = Hoc2(HelloWorld);

//根组件
export class App extends PureComponent {
	render() {
		return (
			<div>
				<h4>App</h4>
				<MyHelloWorldFun />
				<MyHelloWorldFun2 />
			</div>
		);
	}
}

 

3. 应用-props的增强

不修改原有代码的情况下,添加新的props

 (1). 调用Home组件, 传入banners,传入一个数组:["test1", "test2"]

 (2). Home组件是调用高阶组件 enhancedUserInfo , 传入一个函数式组件后,生成的一个组件,通过props获取传入的属性, 包括高阶组件内部属性的传入

 (3). 在高阶组件内部,绑定props传递的属性 和 内部state的属性

高阶组件: 

/**
 * 高阶组件
 * @param {Component} OriginComponent 传入的组件
 * @returns 返回的组件
 */
function enhancedUserInfo(OriginComponent) {
	class NewComponent extends PureComponent {
		constructor(props) {
			super(props);
			this.state = {
				userInfo: {
					name: "ypf",
					age: 18,
				},
			};
		}
		render() {
			return <OriginComponent {...this.props} {...this.state.userInfo} />;
		}
	}

	return NewComponent;
}

export default enhancedUserInfo;

App组件: 

// 调用高阶组件,传入要给函数式组件
const Home = enhancedUserInfo(function (props) {
	return (
		<h2>
			Home: {props.banners}-{props.name}-{props.age}
		</h2>
	);
});

export class App extends PureComponent {
	render() {
		return (
			<div>
				<h1>App</h1>
				<Home banners={["test1", "test2"]} />
			</div>
		);
	}
}

 

4. 应用-Context共享

   App组件中ThemeContext共享数据,包裹Produce组件, 一般是在Produce组件中使用 <ThemeContext.Consumer>来获取数据,

这里将 <ThemeContext.Consumer> 抽离到一个高阶组件theme_context.js中

theme_context.js:

import { createContext } from "react";

const ThemeContext = createContext();

export default ThemeContext;

App组件: 

export class App extends PureComponent {
	render() {
		return (
			<div>
				<ThemeContext.Provider value={{ color: "red", size: 30 }}>
					<Product></Product>
				</ThemeContext.Provider>
			</div>
		);
	}
}

 

5. 应用-登录鉴权

  App组件中引用购物车组件Cart, 封装高阶组件loginAuth, 内部处理用来处理token是否存在。

  (1). loginAuth高阶组件内部使用:函数式组件 props=>{} 的形式

  (2). 可以调用this.forceUpdate()  强制重新执行render函数

高阶组件:

/**
 * 高阶组件--登录鉴权 (这里内部采用的是函数式组件)
 * @param {*} OriginComponent
 * @returns
 */
function loginAuth(OriginComponent) {
	return props => {
		console.log("------loginAuth--------");
		const token = localStorage.getItem("token");
		if (token) {
			return <OriginComponent {...props}></OriginComponent>;
		} else {
			return <h3>请先登录</h3>;
		}
	};
}

export default loginAuth;

App组件: 

export class App extends PureComponent {
	login() {
		localStorage.setItem("token", "xxxx");

		// 强制重新执行render函数
		this.forceUpdate();
	}
	render() {
		return (
			<div>
				App
				<Cart></Cart>
				<button onClick={() => this.login()}>登录</button>
			</div>
		);
	}
}

6. 应用-生命周期

   APP中引用Details组件,要统计这个组件的渲染时间,把这块逻辑抽离一个高阶组件log_render_time

高阶组件:

/**
 * 高阶组件--统计渲染时间
 * @param {*} OriginComponent
 * @returns
 */
function logRenderTime(OriginComponent) {
	// 直接返回的这种形式可以省略类名
	return class extends PureComponent {
		// 这个周期不建议使用,这里仅仅是为了演示, 因为这个生命周期已经废弃了
		UNSAFE_componentWillMount() {
			this.beginTime = new Date().getTime();
		}

		componentDidMount() {
			this.endTime = new Date().getTime();
			const interval = this.endTime - this.beginTime;
			console.log(
				`当前${OriginComponent.name}页面花费了${interval}ms渲染完成!`
			);
		}

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

 

二. 动画详解

1. 说明

     React社区为我们提供了react-transition-group用来完成过渡动画。 React曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护,形成了现在的 react-transition-group, 这个库可以帮助我们方便的实现组件的 入场 和 离场 动画,

 使用时需要进行额外的安装:【npm install react-transition-group】

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

 (1). Transition

     该组件是一个和平台无关的组件(不一定要结合CSS);  在前端开发中,我们一般是结合CSS来完成样式,所以比较常用的是CSSTransition;

 (2). CSSTransition

     在前端开发中,通常使用CSSTransition来完成过渡动画效果

 (3).SwitchTransition

     两个组件显示和隐藏切换时,使用该组件

 (4).TransitionGroup

     将多个动画组件包裹在其中,一般用于列表中元素的动画;

 

2. CSSTransition

(1). 说明

     CSSTransition是基于Transition组件构建的:

(2). 三种状态

    CSSTransition执行过程中,有三个状态:appear、enter、exit;

    它们有三种状态,需要定义对应的CSS样式:

    第一类,开始状态:对于的类是-appear、-enter、exit;

    第二类:执行动画:对应的类是-appear-active、-enter-active、-exit-active;

    第三类:执行结束:对应的类是-appear-done、-enter-done、-exit-done;

(3). 常见属性

  A. in:触发进入或者退出状态

    如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉;

    当in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;

    当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;

  B. classNames:动画class的名称

    决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done;

  C. timeout:过渡动画的时间

  D. appear:是否在初次进入添加动画(需要和in同时为true)

  E. unmountOnExit:退出后卸载组件

  F. CSSTransition对应的钩子函数:主要为了检测动画的执行过程,来完成一些JavaScript的操作

    onEnter:在进入动画之前被触发;

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

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

核心代码:

查看代码
 import React, { PureComponent, createRef } from "react";
import "./style.css";
import { CSSTransition } from "react-transition-group";

export class App extends PureComponent {
	constructor() {
		super();
		this.state = { isShow: true };
		this.myRef = createRef();
	}
	render() {
		const { isShow } = this.state;
		console.log(isShow);

		return (
			<div>
				<button onClick={() => this.setState({ isShow: !isShow })}>切换</button>
				{/* 
                   classNames、timeout 必填的
                   CSSTransition 和 内部组件必须同时绑定一个ref对象
                */}

				{/* 下面是用来测试钩子函数 */}
				<CSSTransition
					nodeRef={this.myRef}
					in={isShow}
					classNames="ypf"
					timeout={2000}
					unmountOnExit={true}
					appear
					onEnter={() => console.log("开始进入动画")}
					onEntering={() => console.log("执行进入动画")}
					onEntered={() => console.log("执行进入结束")}
					onExit={() => console.log("开始离开动画")}
					onExiting={() => console.log("执行离开动画")}
					onExited={() => console.log("执行离开结束")}
				>
					<div ref={this.myRef}>
						<h3>测试动画啊</h3>
						<h3>hello ypf</h3>
					</div>
				</CSSTransition>
			</div>
		);
	}
}

export default App;

 

3. 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属性;(保证key唯一性就可以了)

核心代码:

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

export class App extends PureComponent {
	constructor() {
		super();
		this.state = { isLogin: true };
	}
	change() {
		this.setState({ isLogin: !this.state.isLogin });
	}
	render() {
		const { isLogin } = this.state;

		return (
			<div>
				<SwitchTransition mode="out-in">
					<CSSTransition
						key={isLogin ? "myExitId" : "myLoginId"}
						classNames="login"
						timeout={1000}
					>
						<button onClick={() => this.change()}>
							{isLogin ? "退出" : "登录"}
						</button>
					</CSSTransition>
				</SwitchTransition>
			</div>
		);
	}
}

export default App;

 

4. TransitionGroup

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

核心代码:

  

查看代码
 import React, { PureComponent } from "react";
import { TransitionGroup, CSSTransition } from "react-transition-group";
import "./style.css";

export class App extends PureComponent {
	constructor() {
		super();

		this.state = {
			books: [
				{ id: 111, name: "你不知道JS", price: 99 },
				{ id: 222, name: "JS高级程序设计", price: 88 },
				{ id: 333, name: "Vuejs高级设计", price: 77 },
			],
		};
	}

	addNewBook() {
		const books = [...this.state.books];
		books.push({
			id: new Date().getTime(),
			name: "React高级程序设计",
			price: 99,
		});
		this.setState({ books });
	}

	removeBook(index) {
		const books = [...this.state.books];
		books.splice(index, 1);
		this.setState({ books });
	}

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

		return (
			<div>
				<h2>书籍列表:</h2>
				<TransitionGroup component="ul">
					{books.map((item, index) => {
						return (
							<CSSTransition key={item.id} classNames="book" timeout={1000}>
								<li>
									<span>
										{item.name}-{item.price}
									</span>
									<button onClick={e => this.removeBook(index)}>删除</button>
								</li>
							</CSSTransition>
						);
					})}
				</TransitionGroup>
				<button onClick={e => this.addNewBook()}>添加新书籍</button>
			</div>
		);
	}
}

export default App;

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2023-05-10 16:40  Yaopengfei  阅读(69)  评论(0编辑  收藏  举报