高阶组件的概念
高阶组件就是接受一个组件作为参数并返回一个新组件的函数(也就是说高阶组件是一个函数,并不是组件)
高阶组件的实现
先来看看要做的案例的效果是怎么样的,下图效果中
第一步:对组件进行拆分首先是外层相同的框为A组件,里面内容分别为B组件和C组件
第二步:代码实现,首先搭建项目,然后分别创建A,B,C三个普通的组件
import React, { Component } from 'react'
export default class A extends Component {
render() {
return (
<div>这是A组件</div>
)
}
}
import React, { Component } from 'react'
export default class B extends Component {
render() {
return (
<div><img src={require('../images/Jietu2.jpg')} alt="" /></div>
)
}
}
import React, { Component } from 'react'
export default class C extends Component {
render() {
return (
<div><img src={require('../images/Jietu1.jpg')} alt="" /></div>
)
}
}
第三步:然后将这三个组件在App.js中引入
import React from 'react';
import './App.css';
import A from './components/A'
import B from './components/B'
import C from './components/C'
function App() {
return (
<div className="App">
<A />
<B />
<C />
</div>
);
}
export default App;
创建高阶组件:A组件是B组件和C组件共用的一个组件,先来实现A组件。首先定义一个函数(这个函数接受一个参数(组件)),且返回一个组件
import React, { Component } from 'react'
function A(WrappedComponent) {
return class A extends Component {
render() {
return (
<div className='a-container'>
<div className='header'>
<div>提示</div>
<div>X</div>
</div>
<div>
<WrappedComponent />
</div>
</div>
)
}
}
}
export default A
调用高阶组件:然后在B组件中进行调用这个A组件函数
import React, { Component } from 'react'
import A from './A'
class B extends Component {
render() {
return (
<div><img src={require('../images/Jietu2.jpg')} alt="" /></div>
)
}
}
export default A(B)
调用高阶组件:在C组件中进行调用这个A组件函数,使用装饰器方式进行调用,步骤如下:
第一步:运行package.json运行eject脚本,暴露输出配置项
第二步:安装依赖
yarn add babel-preset-stage-2
yarn add babel-preset-react-native-stage-0
npm install babel-preset-react-native-stage-0
第三步:根目录新建.babelrc文件
{
"presets":["react-native-stage-0/decorator-support"]
}
第四步:export default A(C)改成C,并直接在class上写上@A注解
import React, { Component } from 'react'
import A from './A'
@A
class C extends Component {
render() {
return (
<div><img src={require('../images/Jietu1.jpg')} alt="" /></div>
)
}
}
export default C
有种可能,高阶组件A中的结构是一样的,但内容有可能有变化,这时候被包裹组件可以传递参数给高阶组件
首先修改高阶组件A,将原来的函数在封装一层,并且定义参数
import React, { Component } from 'react'
export default (title) => WrappedComponent => class A extends Component {
render() {
return (
<div className='a-container'>
<div className='header'>
<div>{title}</div>
<div>X</div>
</div>
<div>
<WrappedComponent />
</div>
</div>
)
}
}
在被包裹的组件中进行调用这个高阶组件(其实就是函数)
import React, { Component } from 'react'
import A from './A'
class B extends Component {
render() {
return (
<div>
<img src={require('../images/Jietu2.jpg')} alt="" />
</div>
)
}
}
export default A('提示')(B)
import React, { Component } from 'react'
import A from './A'
class C extends Component {
render() {
return (
<div><img src={require('../images/Jietu1.jpg')} alt="" /></div>
)
}
}
export default A('警告')(C)
高阶组件的应用—代理方式
返回的新组件类直接继承自React.Component类,新组件扮演的角色传入参数组件的一个代理,在新组件的render函数中,将被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全部转手给了被包裹的组件;主要应用有:操作Props,访问ref,抽取状态,包装组件
操作Props
在App.js中引入B组件并且传递props参数
import React from 'react';
import './App.css';
import A from './components/A'
import B from './components/B'
import C from './components/C'
function App() {
return (
<div className="App">
<A />
<B name={'张三'} age={18} />
<C />
</div>
);
}
export default App;
在高阶组件A中
import React, { Component } from 'react'
function A(WrappedComponent) {
return class A extends Component {
render() {
return (
<div className='a-container'>
<div className='header'>
<div>提示</div>
<div>X</div>
</div>
<div>
<WrappedComponent {...this.props} />
</div>
</div>
)
}
}
}
export default A
最后在B组件中使用传递过来的props
import React, { Component } from 'react'
import A from './A'
class B extends Component {
render() {
return (
<div>
我的名字叫:{this.props.name}
<br />
我的年龄是:{this.props.age}
<br />
<img src={require('../images/Jietu2.jpg')} alt="" />
</div>
)
}
}
export default A(B)
通过高阶组件对B组件的props属性进行修改(也就是通过高阶组件给被包裹的组件B添加props属性)
import React, { Component } from 'react'
function A(WrappedComponent) {
return class A extends Component {
render() {
return (
<div className='a-container'>
<div className='header'>
<div>提示</div>
<div>X</div>
</div>
<div>
<WrappedComponent sex={'男'} {...this.props} />
</div>
</div>
)
}
}
}
export default A
import React, { Component } from 'react'
import A from './A'
class B extends Component {
render() {
return (
<div>
我的名字叫:{this.props.name}
<br />
我的年龄是:{this.props.age}
<br />
我的性别是:{this.props.sex}
<br />
<img src={require('../images/Jietu2.jpg')} alt="" />
</div>
)
}
}
export default A(B)
那么通过高阶组件对B组件中的props属性进行删除
import React, { Component } from 'react'
function A(WrappedComponent) {
return class A extends Component {
render() {
const {age, ...otherProps} = this.props
return (
<div className='a-container'>
<div className='header'>
<div>提示</div>
<div>X</div>
</div>
<div>
<WrappedComponent sex={'男'} {...otherProps} />
</div>
</div>
)
}
}
}
export default A
import React, { Component } from 'react'
import A from './A'
class B extends Component {
render() {
return (
<div>
我的名字叫:{this.props.name}
<br />
我的年龄是:{this.props.age}
<br />
我的性别是:{this.props.sex}
<br />
<img src={require('../images/Jietu2.jpg')} alt="" />
</div>
)
}
}
export default A(B)
访问ref(最好不要用,容易出问题)
在C组件中定义一个方法,在高阶组件A中能调用到,首先在C组件中定义一个方法
import React, { Component } from 'react'
import A from './A'
class C extends Component {
getName() {
return '我是C组件'
}
render() {
return (
<div><img src={require('../images/Jietu1.jpg')} alt="" /></div>
)
}
}
export default A(C)
在高阶组件A中进行调用,调用的时候注意进行判空处理
import React, { Component } from 'react'
function A(WrappedComponent) {
return class A extends Component {
refc(instance) { //instance就是WrappedComponent的实例
if (instance.getName) {
console.log(instance.getName())
}
}
render() {
const {age, ...otherProps} = this.props
return (
<div className='a-container'>
<div className='header'>
<div>提示</div>
<div>X</div>
</div>
<div>
<WrappedComponent sex={'男'} {...otherProps} ref={this.refc.bind(this)} />
</div>
</div>
)
}
}
}
export default A
抽取状态
比如在B组件中有一个input输入框,那么要获取输入框的内容(受控组件),如下
import React, { Component } from 'react'
import A from './A'
class B extends Component {
constructor(props){
super(props)
this.state = {
value: ''
}
}
changeInput = (event) => {
const value = event.target.value
this.setState({
value
})
}
render() {
return (
<div>
<input type='text' value={this.state.value} onInput={this.changeInput} />
<br />
我的名字叫:{this.props.name}
<br />
我的年龄是:{this.props.age}
<br />
我的性别是:{this.props.sex}
<br />
<img src={require('../images/Jietu2.jpg')} alt="" />
</div>
)
}
}
export default A('提示')(B)
但是如果有很多像B组件这样的,那么每个组件都要写,这时候可以使用高阶组件传进来,B组件的input中只要接收Props即可
import React, { Component } from 'react'
import A from './A'
class B extends Component {
render() {
return (
<div>
<input type='text' {...this.props} />
<br />
我的名字叫:{this.props.name}
<br />
我的年龄是:{this.props.age}
<br />
我的性别是:{this.props.sex}
<br />
<img src={require('../images/Jietu2.jpg')} alt="" />
</div>
)
}
}
export default A('提示')(B)
在高阶组件中定义一个props并且传给包裹组件
import React, { Component } from 'react'
export default (title) => WrappedComponent => class A extends Component {
constructor(props){
super(props)
this.state = {
value: ''
}
}
onInputChange = (event) => {
const value = event.target.value
this.setState({
value
})
}
render() {
const {age, ...otherProps} = this.props
const newProps = {
value: this.state.value,
onInput: this.onInputChange
}
return (
<div className='a-container'>
<div className='header'>
<div>{title}</div>
<div>X</div>
</div>
<div>
<WrappedComponent sex={'男'} {...otherProps} {...newProps} />
</div>
</div>
)
}
}
包装组件
以上列子中就是包装组件,包装组件的好处就是可以将所有被包裹的组件公共的地方抽取出来,代码也会比较优雅
高阶组件的应用—反向继承方式
采用继承关联作为参数的组件和返回的组件,假如传入的组件参数是WrappedComponent,那么返回的组件就是直接继承自WrappedComponent,主要应用有:操作Props,操作生命周期函数
操作Props
操作生命周期函数
高阶组件使用出现的问题
高阶组件案例—实现一个tabbar
页面效果
创建项目并且实现静态布局
首先使用脚手架创建项目,并且将图片的字体图标资源拷贝到项目中(直接在阿里字体图标库找几个图标),在App.js中引入字体图标库
然后创建一个文件夹:component,创建tabbar文件夹(index.js和index.css),实现静态的布局。样式文件直接拷贝下面:
.iconfont {
font-size: 28px !important;
}
.tabbar-children{
margin-bottom: 50px;
}
.tabbar {
background: #ffffff;
height: 50px;
position: fixed;
bottom: 0;
width: 100%;
border: 1px solid rgba(204, 204, 204, 0.43);
padding: 5px 0;
text-align: center;
}
.tabbar-content {
display: flex;
}
.tarbar-item {
flex: 1;
}
.active {
color: red !important;
}
import React, {Component} from 'react';
// import { Link } from 'react-router-dom';
import './index.css'
const tarbarArr = [
{
img: 'icon-home',
text: '首页',
link: '/home'
},
{
img: 'icon-fenlei',
text: '分类',
link: '/category'
},
{
img: 'icon-gouwuche',
text: '购物车',
link: '/car'
},
{
img: 'icon-msnui-user',
text: '我的',
link: '/user'
},
]
class Index extends Component {
render() {
return (
<div className='tabbar'>
<div className='tabbar-content'>
{
tarbarArr.map((item, index) => (
<div key={index} className='tarbar-item'>
<div className={'iconfont ' + item.img}></div>
<div>{item.text}</div>
</div>
))
}
</div>
</div>
);
}
}
export default Index;
效果如下:
给每个tabbar添加点击事件
import React, {Component} from 'react';
// import { Link } from 'react-router-dom';
import './index.css'
const tarbarArr = [
{
img: 'icon-home',
text: '首页',
link: '/home'
},
{
img: 'icon-fenlei',
text: '分类',
link: '/category'
},
{
img: 'icon-gouwuche',
text: '购物车',
link: '/car'
},
{
img: 'icon-msnui-user',
text: '我的',
link: '/user'
},
]
class Index extends Component {
constructor(props) {
super(props)
this.state = {
index: 0
}
}
itemChange = (index) => {
this.setState({
index
})
}
render() {
return (
<div className='tabbar'>
<div className='tabbar-content'>
{
tarbarArr.map((item, index) => (
<div key={index} className={'tarbar-item ' + (this.state.index === index ? 'active' : '')} onClick={() => this.itemChange(index)}>
<div className={'iconfont ' + item.img}></div>
<div>{item.text}</div>
</div>
))
}
</div>
</div>
);
}
}
export default Index;
创建每个tabbar对应的页面
import React, { Component } from 'react';
class Home extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/home.png')} alt="" />
</div>
);
}
}
export default Home;
import React, { Component } from 'react';
class Category extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/category.png')} alt="" />
</div>
);
}
}
export default Category;
import React, { Component } from 'react';
class Car extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/car.png')} alt="" />
</div>
);
}
}
export default Car;
import React, { Component } from 'react';
class User extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/user.png')} alt="" />
</div>
);
}
}
export default User;
src下创建路由文件router.js,并且在App.js中引入
import React from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Home from './pages/home'
import Category from './pages/category'
import Car from './pages/car'
import User from './pages/user'
export default () => (
<BrowserRouter>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/home' component={Home} />
<Route path='/category' component={Category} />
<Route path='/car' component={Car} />
<Route path='/user' component={User} />
</Switch>
</BrowserRouter>
)
import React from 'react';
import RouterMap from './router';
import './static/iconfont.css';
import './App.css';
function App() {
return (
<div className="App">
<RouterMap />
</div>
);
}
export default App;
将tabbar组件改造成高阶组件并且在每个页面进行调用
import React, {Component} from 'react';
// import { Link } from 'react-router-dom';
import './index.css'
const tarbarArr = [
{
img: 'icon-home',
text: '首页',
link: '/home'
},
{
img: 'icon-fenlei',
text: '分类',
link: '/category'
},
{
img: 'icon-gouwuche',
text: '购物车',
link: '/car'
},
{
img: 'icon-msnui-user',
text: '我的',
link: '/user'
},
]
const Tabbar = (WrappedComponent) => class Tabbar extends Component{
constructor(props) {
super(props)
this.state = {
index: 0
}
}
itemChange = (index) => {
this.setState({
index
})
}
render() {
return (
<div className='tabbar-container'>
<div className='tabbar-children'>
<WrappedComponent />
</div>
<div className='tabbar'>
<div className='tabbar-content'>
{
tarbarArr.map((item, index) => (
<div key={index} className={'tarbar-item ' + (this.state.index === index ? 'active' : '')} onClick={() => this.itemChange(index)}>
<div className={'iconfont ' + item.img}></div>
<div>{item.text}</div>
</div>
))
}
</div>
</div>
</div>
);
}
}
export default Tabbar;
import React, { Component } from 'react';
import Tabbar from '../components/tabbar'
class Home extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/home.png')} alt="" />
</div>
);
}
}
export default Tabbar(Home);
import React, { Component } from 'react';
import Tabbar from '../components/tabbar';
class Car extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/car.png')} alt="" />
</div>
);
}
}
export default Tabbar(Car);
import React, { Component } from 'react';
import Tabbar from '../components/tabbar';
class User extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/user.png')} alt="" />
</div>
);
}
}
export default Tabbar(User);
import React, { Component } from 'react';
import Tabbar from '../components/tabbar';
class Category extends Component {
render() {
return (
<div>
<img className='bg' src={require('../static/images/category.png')} alt="" />
</div>
);
}
}
export default Tabbar(Category);
给每个tabbar添加路由和路由切换
import React, {Component} from 'react';
import { Link } from 'react-router-dom';
import './index.css'
const tarbarArr = [
{
img: 'icon-home',
text: '首页',
link: '/home'
},
{
img: 'icon-fenlei',
text: '分类',
link: '/category'
},
{
img: 'icon-gouwuche',
text: '购物车',
link: '/car'
},
{
img: 'icon-msnui-user',
text: '我的',
link: '/user'
},
]
const Tabbar = (WrappedComponent) => class Tabbar extends Component{
constructor(props) {
super(props)
this.state = {
index: 0
}
}
itemChange = (index) => {
this.setState({
index
})
}
render() {
const url = window.location.href
return (
<div className='tabbar-container'>
<div className='tabbar-children'>
<WrappedComponent />
</div>
<div className='tabbar'>
<div className='tabbar-content'>
{
tarbarArr.map((item, index) => (
<Link to={item.link} key={index} className={'tarbar-item ' + (url.indexOf(item.link) > -1 ? 'active' : '')} onClick={() => this.itemChange(index)}>
<div className={'iconfont ' + item.img}></div>
<div>{item.text}</div>
</Link>
))
}
</div>
</div>
</div>
);
}
}
export default Tabbar;