React无状态组件、有状态组件配合高阶组件的使用

3,119 阅读4分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

一、概念

1. 无状态组件

无状态组件(展示组件,函数式组件):就是一个函数没有props,没有生命周期, 就是一个简单的视图函数,没有业务逻辑更纯粹的展示UI。例子:

function NullStatus() {
    return (
        <div>无状态组件</div>
    )
}

2. 有状态组件

有状态组件(容器组件,类组件) :标准使用模式此函数继承了React里面的组件和props,可以使用生命周期可以再里面写业务逻辑, 可以再里面做任何事情.例子:

class HocComponent extends React.Component {
    constructor() {
        super();
        this.state = {
            name: '张三'
        }
    }
    
    render() {
        return (
            <div>有状态组件</div>
        )
    }
}

3. 在有状态组件里使用无状态组件

import React from 'react'
// import Css from './assets/css/app.css'

// 无状态组件
function NoStatusComponent(props) {
    return (
        <div>
            {props.title}
            <button type={"button"} onClick={props.sendParent.bind(this,'我是無狀態組件传过来的值')}>给父组件传值</button>
        </div>
    )
}


class App extends React.Component {
    // 构造器
    constructor() {
        super();
        this.state = {

        };
    }

    // 处理子组件中的值
    handleChildV(e) {
        console.log(e)
    }


    // 渲染dom
    render() {
        return (
            <div className="">
                <NoStatusComponent title={'无状态组件'} sendParent={this.handleChildV.bind(this)}>

                </NoStatusComponent>
            </div>
        )
    }
}
export default App;

image.png

3. 高阶组件

  • 高阶组件: 其实就是高阶函数, 我们第一一个函数, 里面返回一个有状态组件, 就是高阶组件
  • 高阶组件就像我们吃火锅的锅底, 可以在里面加羊肉、牛肉、蔬菜等各种食物。锅底相当于业务逻辑,食物相当于UI展示,这样可以使我们的业务逻辑层和UI层分类,代买更清晰, 更适合多人开发和维护。
  • 例子, 在html中函数中返回一个函数, 再调用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <script>
    function hoc() {
      return function hocComponent(args) {
        console.log(args)
      }
    }
    hoc()('参数')
  </script>
</body>
</html>
  • 效果

image.png

3.1 高阶组件的分类

属性代理模式

属性代理最常见的高阶组件的使用方式。它通过做一些操作,将被包裹组件的props和新生产的props一起传递给辞组件,这称之未属性代理。

反向继承的方式

这种方式返回的React组件继承了被传入的组件,所以它能够访问到的区域,权限更更多,相比属性代理方式,他更像带入组织内部,对其进行修改。

3.2 在react中使用代理模式

新建一个hoc组件, 返回的是一个有状态组件,和一个title

import React from 'react'

export default function Hoc(WithComponent,title) {
    return class HocComponent extends React.Component {
        render() {
            return (
                <React.Fragment>
                    <div>{title}</div>
                    <WithComponent title={'我是代理高阶组件'} name={'张三'}></WithComponent>
                </React.Fragment>
            )
        }
    }
}

在App.js中调用

import React from 'react'
// import Css from './assets/css/app.css'
import Proxy from './component/hoc/proxy'

// 无状态组件
function NoStatusComponent(props) {
    return (
        <div>
            {props.title}
            <button type={"button"} onClick={props.sendParent.bind(this,'我是無狀態組件传过来的值')}>给父组件传值</button>
        </div>
    )
}


class App extends React.Component {
    // 构造器
    constructor() {
        super();
        this.state = {

        };
    }

    // 处理子组件中的值
    handleChildV(e) {
        console.log(e)
    }


    // 渲染dom
    render() {
        return (
            <div className="">
                <NoStatusComponent title={'无状态组件'} sendParent={this.handleChildV.bind(this)}>
                </NoStatusComponent>
                {this.props.title}
                <br/>
                {this.props.name}
            </div>
        )
    }
}
export default Proxy(App,'我是属性代理高阶组件的参数');

代理出来了, 通过proxy(App,title) title给代理组件传值, 通过 WithComponent给被代理的组件传值

image.png

3.3 在react中使用继承模式

class 组件不要继承react 继承传入的组件就可以了

export default function Hoc(WithComponent,title) {
    return class HocComponent extends WithComponent {
        render() {
            return (
                <React.Fragment>
                    <div>{title}</div>
                    <WithComponent title={'我是被继承高阶组件'} name={'张三'}></WithComponent>
                </React.Fragment>
            )
        }
    }
}

效果

image.png

区别

继承比proxy更强大, 可以获取被继承组件中的属性, 例如在继承组件中获取父组件的state

  <div>{this.state.age}</div>

效果

image.png

4. 使用ES6的方式使用代理的高阶hoc组件

App.js中 使用箭头函数代表一个组件

import React from 'react'
import Hoc from './component/hoc/hoc'

let Login = Hoc(
    (props) => {
        console.log(props)
        return (
            <React.Fragment>
                <h1>会员登录</h1>
                <form>
                    <label>
                        用户名:
                        <input value={props.username} onChange={e => {
                            console.log(e)}}/>
                    </label>
                    <br/>
                    <div style={{marginTop:' 10px'}}></div>
                    <label>
                        密码:
                        <input value={props.password} onChange={e => {
                            console.log(e)
                        }}/>
                    </label>
                </form>
            </React.Fragment>

        )
    }
)

class App extends React.Component {
    // 构造器
    constructor() {
        super();
        this.state = {

        };
    }

    // 渲染dom
    render() {
        return (
            <div className="">
                <Login username={'张三'} password={'123456'} />
            </div>
        )
    }
}
export default App;

hoc.js中 使用 扩展运算符(...)接收所有参数

import React from 'react'

export default function Hoc(WithComponent,title) {
    return class HocComponent extends React.Component {
        render() {
            console.log(this.props)
            return (
                <React.Fragment>
                    <div>{title}</div>
                    <WithComponent {...this.props}></WithComponent>
                </React.Fragment>
            )
        }
    }
}

效果一个简单的登录就被渲染出来了

image.png

ps: ES5和ES6接收参数的区别

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    function demo() {
        console.log(arguments,'es5中的伪数组参数')
        console.log(arguments[0],'es5中的伪数组参数的第一个元素')
        console.log('----伪数组是无法遍历的----')
        console.log(Array.prototype.slice.call(arguments),'es5中伪数组转数组')
        console.log(Array.from(arguments),'es6中的伪数组转数组')
        console.log('-----华丽的分割线----')
    }
    let demoEs6 = (...args) => {
        console.log('----es6中没有arguments对象----')
        console.log(args,'es6接收的就是一个真数组')
    }
    demo(1,2,3,4,5)
    demoEs6(1,2,3,4,5)
</script>
</body>
</html>

效果

image.png

在hoc中使用插槽

 <Login username={'张三'} password={'123456'} >
                    <li>会员一</li>
                    <li>会员二</li>
                </Login>

image.png

用es6和hoc封装一个简单的表单校验组件吧

App.js

import React from 'react'
import Hoc from './component/hoc/hoc'

let Login = Hoc(
    (props) => {
        console.log(props)
        return (
            <React.Fragment>
                <h1>会员登录</h1>
                <form>
                    <label>
                        用户名:
                        <input {...props.username}/>  <span style={{color:'deeppink'}}>{props.isUsernameValid ? '' : '请输入正确的用户名'}</span>
                    </label>
                    <br/>
                    <div style={{marginTop:' 10px'}}></div>
                    <label>
                        密码:
                        <input  {...props.password} type={'password'} /> <span style={{color:'deeppink'}}>{props.isPasswordValid ? '' : '请输入正确的密码'}</span>
                    </label>
                    <br/>
                    <button  type={'button'} onClick={props.submit.bind(this, () => {
                        alert(`提交成功!:  用户名: ${props.username.value} 密码: ${props.password.value}`)
                    })}>登录</button>
                </form>
                <hr/>
                <div>插槽</div>
                {props.children}
            </React.Fragment>
        )
    }
)


let Login2 = Hoc(
    (props) => {
        console.log(props)
        return (
            <React.Fragment>
                <h1>会员登录2</h1>
                <form>
                    <label>
                        用户名:
                        <input {...props.username}/>  <span style={{color:'green'}}>{props.isUsernameValid ? '' : '请输入正确的用户名'}</span>
                    </label>
                    <br/>
                    <div style={{marginTop:' 10px'}}></div>
                    <label>
                        密码:
                        <input  {...props.password} type={'password'} /> <span style={{color:'green'}}>{props.isPasswordValid ? '' : '请输入正确的密码'}</span>
                    </label>
                    <br/>
                    <button  type={'button'} onClick={props.submit.bind(this, () => {
                        alert(`提交成功!:  用户名: ${props.username.value} 密码: ${props.password.value}`)
                    })}>登录</button>
                </form>
                <hr/>
                <div>插槽</div>
                {props.children}
            </React.Fragment>
        )
    }
)

class App extends React.Component {
    // 构造器
    constructor() {
        super();
        this.state = {

        };
    }

    // 渲染dom
    render() {
        return (
            <div className="">
                <Login >
                    <li>会员一</li>
                    <li>会员二</li>
                </Login>
                <br/>
                <Login2>

                </Login2>
            </div>
        )
    }
}
export default App;

hoc.js

import React from 'react'

export default function Hoc(WithComponent,title) {
    return class HocComponent extends React.Component {
        constructor() {
            super();
            this.state = {
                username: '',
                password: '',
                isUsernameValid: true,
                isPasswordValid: true,
            }
        }

        // 改变用户名
        changeUsername(e) {
            this.setState({
                username: e.target.value
            })
        }
        // 改变密码
        changePassword(e) {
            this.setState({
                password: e.target.value
            })
        }

        // 校验数据
        validData() {
            let  flag = true
            // 数据校验
            // 用户名不能为空且大于6位
            if(this.state.username.match(/^\s*$/) || this.state.username.length < 6) {
                flag = false
                this.setState({isUsernameValid: false})
                // return flag
            } else {
                this.setState({isUsernameValid: true})
            }
            // 密码不能为空且大于6位
            if(this.state.password.match(/^\s*$/) || this.state.password.length < 6) {
                flag = false
                this.setState({isPasswordValid: false})
                // return flag
            } else {
                this.setState({isPasswordValid: true})
            }
            return flag
        }

        // 登录
        submitData(callback) {
            let pass = this.validData()
            if(!pass) return
            // 执行成功回调
            if(typeof callback === 'function') {
                callback()
            }
        }

        render() {
            console.log(this.props)
            let newProps = {
                username: {
                    value: this.state.username,
                    onChange: this.changeUsername.bind(this),
                    onBlur: this.validData.bind(this)
                },
                password: {
                    value: this.state.password,
                    onChange: this.changePassword.bind(this),
                    onBlur: this.validData.bind(this)
                },

            }
            return (
                <React.Fragment>
                    <div>{title}</div>
                    <WithComponent {...this.props} {...this.state} {...newProps} submit={this.submitData.bind(this)}></WithComponent>
                </React.Fragment>
            )
        }
    }
}

效果

666.gif