React组件化

392 阅读6分钟

聪明式组件VS傻瓜式组件

聪明式组件是操作数据的组件,傻瓜式组件是只展示数据的组件

我们先写一个聪明式CommentList组件:

class CommentList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      comments: [{id: 1,author: 'facebook',content: 'react好'}]
    }
  }
  componentDidMount () {
    setTimeout(() => {
      this.setState({
        comments: [{id: 2,author: '尤雨溪', content: 'vue更好'},]
      })
    }, 1000);
  }
  render () {
    return (
      <div>
        {this.state.comments.map((item, i) => {
            return (<Comment key={item.id} comments={item}></Comment>)})}
      </div>
    );
  }
}

在聪明式组件CommentList中对数据进行操作,然后在Comment傻瓜式组件对数据进行展示。

Comment傻瓜式组件

function Comment (props) {
  const { id, content, author } = props.comments;
  return (
    <div>
      <p>{id}</p>
      <p>{content}</p>
      <p>{author}</p>
    </div>
  )
}

组件渲染优化

1.shouldComponentUpdate

假设上面的例子我们用setInterval替换setTimeout做轮询。Comment组件将会一直渲染,这时候我们用shouldComponentUpdate函数来判断setState的值前后是否一致来决定是否渲染。

shouldComponentUpdate()包含两个参数。nextProps, nextState。

用即将渲染的数据跟现在的数据作比较。如果一致返回false 不渲染组件,不一致返回true 渲染组件

class Comment extends Component {
  shouldComponentUpdate (nextProps, nextState) {
    //性能优化
    if (nextProps.comment.content === this.props.comment.content) {
      return false
    } else {
      return true
    }
  }
  render () {
    console.log('render');
    const { id, content, author } = this.props.comment;
    console.log(content);
    return (
      <div>
        <p>{id}</p>
        <p>{content}</p>
        <p>{author}</p>
      </div>
    )
  }
}

2.PureComponent

PureComponent 浅比较 比较的是值。当值不变的时候,组件不会重新渲染。但是上面的要传的是一个数组里面包含对象。这个PureComponent是无法识别的,会一直不断的渲染

import React, { Component,PureComponent } from 'react'
class Comment extends PureComponent{
    render(){
        console.log('render');
        const { id, content, author} = this.props.comment;
        return(
            <div>
                <p>{ id }</p>
                <p>{ content }</p>
                <p>{ author }</p>
            </div>
        )
    }
}

这样写会一直渲染。想让组件不重复渲染,需要在聪明式组件传值的时候,将传的值控制为字符串、布尔值等

<Comment key={item.id} id={item.id} author={item.author} content={item.content}></Comment>

再将接收值修改一下

const { id, content, author} = this.props;

但是要传的值一旦多了,这个方法会写的很麻烦。所以可以用解构赋值的方式传值(重要)

<Comment key={item.id} {...item}></Comment>

3.React.memo()

一种高阶组件用法 React.memo()其实跟PureComponent用法差不多,里面传一个函数作为参数

const Comment = React.memo(({id, content, author})=>{
    console.log('render');
    return(
        <div>
            <p>{ id }</p>
            <p>{ content }</p>
            <p>{ author }</p>
        </div>
    )
})

Comment为高阶组件

组件组合

React官方文档有这样一句话:推荐是用组件组合而非继承实现代码重用
这个跟Vue中的插槽用法一样:定义一个函数组件,用props.children作为匿名插槽。其他组件调用该组件,在该组件标签内容内编写要要展示的数据。如下例子

function Dialog(props){
    return(
        <div>
            {/* 匿名插槽 */}
            { props.children }
        </div>
    )
}

function WelcomeDialog(){
    return(
        <Dialog>
            {/* 这些插到Dialoga组件中的props.children */}
            <h3>Hello</h3>
            <p>Welcome组件组合</p>
        </Dialog>
    )
}
export default class QRCodeMapping extends Component {
    render() {
        return (
            <div>
                <WelcomeDialog></WelcomeDialog>
            </div>
        )
    }
}

类似于具名插槽功能

在父组件中的子组件标签内(这里是Dialog)添加标签的属性进行传值。在子组件内使用、渲染。要传的标签属性可以是一个jsx

function Dialog(props){
    return(
        <div style={{border:`3px solid ${props.color}`}}>
            {/* 匿名插槽 */}
            { props.children }
            <div>
            	{/* 具名插槽 */}
                {props.btn}
            </div>
        </div>
    )
}

function WelcomeDialog(){
    const Welcomebtn = <Button type='primary'> 欢迎 </Button>
    return(
    	// 这里在标签属性里传了值、组件
        <Dialog color={'red'} btn={Welcomebtn}>
            {/* 这些插到Dialoga组件中的props.children */}
            <h3>Hello</h3>
            <p>Welcome组件组合</p>
        </Dialog>
    )
}

高阶组件

组件设计的目的:保证组件功能的单一性。
高阶组件不是组件,本质上是一个函数,这个函数接受一个或多个组件,返回一个新组件,则当前组件为高阶组件.

  1. 创建组建高阶组件的函数,在这个函数对组件做处理(传值等操作)
  2. 编写要传的组件,可以用this.props来获取高阶组件函数传的值
  3. 调用高阶组件函数,并且将要传的组件作为参数传递

属性代理

// 本质上是一个函数,这个函数接收一个组件或者多个组件,返回一个新组件
const highOrderCom = (Comp) => {
	//返回一个新组件
	const NewComponent = (props) => {
    	// 属性代理
		const attr = { type:'高阶组件' }
		return <Comp {...props} {...attr}></Comp>
	}
	return NewComponent;
}
class Hoc extends Component {
	render() {
		return (
			<div>
				<h3>{this.props.type}</h3>
			</div>
		)
	}
}
export default highOrderCom(Hoc);

重写组件

const highOrderCom = (Comp) => {
	//返回一个新组件
	return class extends Component{
		constructor(props){
			super(props);
		}
		componentDidMount(){
			console.log('发起fetch请求');
		}
		render(){
			return(
				<Comp {...this.props} type='高阶组件'></Comp>
			)
		}
	}
}

高阶函数 定义:接收的参数是函数或者返回值是函数

  • 常见的:数组的遍历的相关方法、定时器、Promise、高阶组件
  • 作用:实现一个更加强大的动态功能
  • 高阶组件设计的思想也是如此,传入一个组件,返回一个功能更强大的组件

理解高阶组件

  1. 我们为什么需要高阶组件?

    • react高阶组件能够让哦我们写出易于维护的react代码
  2. 高阶组件是什么

    • 本质上是一个函数,这个函数接收一个组件或者多个组件,返回一个新组件

    • 比如:给一个赛亚人,返回一个超级赛亚人

    • y = kx + b

    • x好比是普通组件,k和b就是当前普通组件定制的属性和方法,y就是返回的新组件

  3. 如何实现高阶组件

    1. 属性代理是最常见的实现方式。
    • 好处:常用的方法独立出来,多次复用。

    1. 反向继承
  4. 高阶组件的应用

    1. 页面复用
    2. 权限控制
    3. 打印日志

链式调用与装饰器

写一个打印日志的高阶组件

const withLog = (Comp) => {
	console.log(Comp.name + '渲染了');
	return (props) => {
		return <Comp {...props}></Comp>
	}
}
链式调用 由内到外去执行
export default highOrderCom(withLog(Hoc));
装饰器

下载:

npm install --save-dev babel-plugin-transform-decorators-legacy @babel/plugin-proposal-decorators

配置好后可以这么写

@highOrderCom
@withLog
class Hoc extends Component{....}

等同于highOrderCom(withLog(Hoc))

组件通信Context

Context提供了一个无需为每层组件手动添加props就能在组件树间进行数据传递的方法。

Context设计的⽬的是为了共享那些全局的数据.

Context == vue中的provide和inject react中的provider和consumer

使用Context

  1. React.createContext()创建一个上下文对象 例:
const ThemeContext = React.createContext();
  1. 在要传递数据的组件中用ThemeContext.Provider标签包裹内容,并且在标签属性中定义要传的值。 例:
<ThemeContext.Provider value={this.state.store}>
    <Toolbar></Toolbar>
</ThemeContext.Provider>
  1. 在需要用到Context对象的组件中使用 两种方法

    1. 定制当前的创建的上下文对象 为当前实例的静态属性.
    static contextType = ThemeContext
    

    然后在渲染的方法中使用this.context获取共享的数据
    完整代码示例

import React, { Component } from 'react'
import { Button } from 'antd'
const ThemeContext = React.createContext();
class ThemeBtn extends Component{
    constructor(props){
        super(props);
        console.log(this);
    }
    //定制创建的上下文对象为静态属性
    static contextType = ThemeContext
    render(){
        return(
       	    //this.context来获取context里的数据
            <Button type={this.context.type}>{this.context.name}</Button>
        )
    }
}

function Toolbar(props){
    return <ThemeBtn></ThemeBtn>
}

export default class ContextSimple extends Component {
    constructor(props){
        super(props);
        this.state={
            store:{
                type:'primary',
                name:'按钮'
            }
        }
    }
    render() {
        return (
            // 给上下文对象传值
            <ThemeContext.Provider value={this.state.store}>
                <Toolbar></Toolbar>
            </ThemeContext.Provider>
        )
    }
}
  1. 基于函数渲染 在ThemeBtn 组件中的渲染方法里使用ThemeContext.Consumer标签,内容为一个插值{}里面包含着一个箭头函数,传的值为定义的值,然后直接使用
   class ThemeBtn extends Component{
   	constructor(props){
        	super(props);
        	console.log(this);
    	}
    render(){
        return( 
		<ThemeContext.Consumer>
			{
				//2. 建议使用这种方式 更好理解 基于函数渲染
				(value)=>{
					return <Button type={value.type}>{value.name}</Button>
				}
				//另外一种写法
				//value=><Button type={value.type}>{value.name}</Button>
			}
		</ThemeContext.Consumer>
        )
    }
}