第七节:高阶组件详解 和 动画详解
一. 高阶组件
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。