React(五):受控组件、高阶组件、Portals、Fragment、CSS的编写方式

一、受控组件

在这里插入图片描述

1.什么是受控组件(v-model)

类似vue中的v-model,就是双向数据绑定。

比如下面这两个input框,受控组件的条件就是有value值。

但是只有value值读取state中的值不行,这样input里的东西就不能改了,所以要有onChange事件去修改相应的值,实现输入框和state数据的同步。

export class App extends PureComponent {
    constructor() {
        super();
        this.state = {
            name: 'zzy',
            age: 18,
        }
    }

    changeInput(e) {
        console.log(e.target.value);
        this.setState({
            name: e.target.value
        })
    }

    render() {
        let { name } = this.state;
        return (
            <div>
                {/* 1.受控组件 */}
                <input type="text" value={name} onChange={(e) => this.changeInput(e)}/>
                {/* 2.非受控组件 */}
                <input type="text" />

                <h1>{name}</h1>
            </div>
        )
    }
}

2.收集表单数据:input和单选框

React文档写的非常非常好,去看看

下面是一个提交用户名密码和单选框选中状态的案例

下面这个案例定义了两个输入框,分别是用户名和密码。还定义了一个单选框,有几个需要注意的地方。

  1. for由于对应js关键字,使用htmlFor代替

  2. 受控组件的特点,上面已经提到,必须写value且必须要有onChange事件,且该事件要修改对应的数据。

  3. 修改数据时我们可以去读取e.target.name找到相应的数据,把值修改为 e.target.value,这样的话就不用一个一个修改了。(键值读取变量用[]包裹)

  4. 提交的值我们可以根据表单的类别进行限制,如果是checkbox那么就收集选中状态(checked属性),如果是输入框那么就收集value

  5. 点击按钮提交表单数据的时候,数据已经是最新的了(onChange的时候就setState => render了),那么我们可以拿着最新的数据去发送ajax请求。

export class App extends PureComponent {
    constructor() {
        super();
        this.state = {
            userName: '',
            password: 0,
            isAgree: false,
        }
    }

    submitData() {
        console.log('提交', this.state.userName, this.state.password,this.state.isAgree)
    }

    changeInput(e) {
        console.log(e.target.value);
        //如果拿单选框的数据,那么拿到的是布尔值checked
        const value = e.target.type == 'checkbox' ? e.target.checked : e.target.value;
        this.setState({
            [e.target.name]: value
        })
    }

    render() {
        let { userName, password,isAgree } = this.state;
        return (
            <div>
                <form action="">
                    {/* 1.用户名 */}
                    <label htmlFor="userName">用户名</label>
                    <input
                        type="text"
                        id='userName'
                        name='userName'
                        value={userName}
                        onChange={(e) => this.changeInput(e)}
                    />
                    {/* 2.密码 */}
                    <label htmlFor="password"></label>
                    <input
                        type="password"
                        id='password'
                        name='password'
                        value={password}
                        onChange={(e) => this.changeInput(e)}
                    />

                    {/* 3.单选框 */}
                    <input
                        type="checkbox"
                        name="isAgree"
                        id="man"
                        value="同意"
                        checked={isAgree}
                        onChange={(e) => this.changeInput(e)}
                    />
                    <label htmlFor="man">同意上述协议</label>
                </form>
                <button onClick={() => this.submitData()}>提交表单数据</button>
            </div>
        )
    }
}

在这里插入图片描述

3.收集表单数据:下拉框

<select>
   <option value="apple">苹果</option>
   <option selected value="pear"></option>
   <option value="peach">桃子</option>
</select>

请注意,由于 selected 属性的缘故,莉选项默认被选中。React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:

export class App extends PureComponent {
    constructor() {
        super();
        this.state = {
            fruit: 'apple'
        }
    }

    submitData() {
        console.log('提交', this.state.fruit)
    }

    handleFruitChange(e) {
        this.setState({
            fruit: e.target.value
        })
    }   

    render() {
        let { fruit } = this.state;
        return (
            <div>
                <form action="">
                    <select value={fruit} onChange={(e) => this.handleFruitChange(e)}>
                        <option value="apple">苹果</option>
                        <option value="pear"></option>
                        <option value="peach">桃子</option>
                    </select>
                </form>
                <button onClick={() => this.submitData()}>提交表单数据</button>
            </div>
        )
    }
}

这里如果select标签加了个multiple,那么意味着可以收集多个value,我们可以借助e.target.selectedOptions这个伪数组获取所有被选中的选项,拿到它们的值

handleFruitChange(e) {
    const options = Array.from(e.target.selectedOptions);
    const values = options.map(item => item.value);
    this.setState({
        fruit: values
    })
}  

二、非受控组件

一般不用非受控组件,因为这玩意儿就是直接操作DOM

  • 在非受控组件中通常使用defaultValue来设置默认值;
  • 如果要使用非受控组件中的数据,那么我们需要使用 ref 来从DOM节点中获取表单数据。
export default class App extends PureComponent {
  constructor(props) {
    super(props);
 
    this.myRef = createRef();
  }
 
  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <label htmlFor="username">
            用户: 
            <input type="text" id="username" defaultValue='默认值' ref={this.myRef}/>
          </label>
          <input type="submit" value="提交"/>
        </form>
      </div>
    )
  }
 
  handleSubmit(event) {
    event.preventDefault();
    console.log(this.myRef.current.value);
  }
}

三、高阶组件

高阶函数:接收一个函数作为参数,或者返回一个函数。满足两个条件之一。
JavaScript中比较常见的filter、map、reduce都是高阶函数。

1.什么是高阶组件

高阶组件的英文是 Higher-Order Components,简称为 HOC。
官方的定义:高阶组件是参数为组件,返回值为新组件函数
也就是说,首先, 高阶组件本身不是一个组件,而是一个函数;其次,这个函数的参数是一个组件,返回值也是一个组件

举个例子:

//高阶组件:接收一个组件作为参数,返回另一个组件
function high(Component) {
    class newComponent extends React.PureComponent {
        render() {
            return (
                //可以做一些处理,比如统一在这里添加name属性
                <Component name='zzy'/>
            )
        }
    }
    return newComponent;
}

//需要添加name属性的组件
class HelloWorld extends React.PureComponent {
    render() {
        console.log(this.props.name);//输出name
        return (
            <div>HelloWorld</div>
        )
    }
}

//1.返回一个类组件
const Dj = high(HelloWorld)

export class App extends React.PureComponent {
    render() {
        return (
            <div>
                {/* 2.对类进行实例化 */}
                <Dj/>
            </div>
        )
    }
}

2.高阶组件的应用1

看下面这个例子,定义两个普通组件Component1、Component2,我们要给这两个组件的props上塞点数据,让它实例化的时候带着这个数据去展示,怎么做呢?

答案是定义高阶组件enhanceUserInfo,返回一个新组件,这个新组件把你传进来的组件作为儿子,并且把自己身上的数据给他{...this.state.userInfo},这样的话经过处理的Component1、Component2就可以拥有nameage,可以通过props读到。

function enhanceUserInfo(OriginComponent) {
    class NewComponent extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = {
                userInfo: {
                    name: 'zzy',
                    age: 18
                }
            }
        }

        render() {
            //把一些数据塞到你传进来的组件中
            return <OriginComponent {...this.props} {...this.state.userInfo}/>
        }
    }
    return NewComponent
}

function Component1(props) {
    return <h1>Component1: {props.name}-{props.age}-{props.flag}</h1>
}
const NewCom1 = enhanceUserInfo(Component1);

//也可以这样写
const NewCom2 = enhanceUserInfo(function(props) {
    return <h1>Component2: {props.name}-{props.age}-{props.sex}</h1>
})



export class HOC extends PureComponent {
    render() {
        return (
            <div>
                <h2>HOC</h2>
                {/* 如果在这里传值的话,传给的是NewComponent */}
                <NewCom1 flag='标识'/>
                <NewCom2 sex='男'/>
            </div>
        )
    }
}

还有个问题,如果我们往新组件中添加属性的话,是传给谁了呢?答案是传给了高阶组件中新组件newComponentprops中,我们可以通过{...this.props}再把它们传给你塞进去的组件OriginComponent

3.高阶组件的应用2-注入Context

细品一下,其实高阶组件的作用就是在我们要处理的组件上边包个父组件,父组件给它加点料,然后再把父组件扔出来用。

比如我们如果在App中使用context给下面两个子组件传值:

export class App extends PureComponent {
    render() {
        return (
            <div>
                <h1>父组件App</h1>
                <myContext.Provider value={{ name: 'zzy', age: 18 }}>
                    <HOC/>
                    <HOC2/>
                </myContext.Provider>
            </div>
        )
    }
}

那么很多组件都要接到这个数据,如果我们分别取每个子组件定义Consumer去接那真的是非常麻烦,此时我们就可以把注入value的这个操作封装一下,也就是使用高阶组件。

function enhanceContext(Component) {
    class NewComponent extends PureComponent {
        render() {
            return (
                <div>
                    <myContext.Consumer>
                        {
                            value => {
                                return <Component {...value} />
                            }
                        }
                    </myContext.Consumer>
                </div>
            )
        }
    }
    return NewComponent;
}

那么我们在HOC和HOC2中就可以通过高阶组件注入value,并拿到传进来的value值:

export class HOC extends PureComponent {
    render() {
        return (
            <div>
                <h2>子组件1HOC</h2>
                <h3>HOC拿到传过来的value:{this.props.name + this.props.age}</h3>
            </div>
        )
    }
}

const newHOC = enhanceContext(HOC);
export default newHOC;
function HOC2(props) {
  return (
    <div>
        <h2>子组件2HOC2</h2>
        <h3>HOC2拿到穿过来的value:{props.name}-{props.age}</h3>
    </div>
  )
}

export default enhanceContext(HOC2);

4.高阶组件的应用3-登录鉴权

如果我们要实现,HOC和HOC2等组件在登录之后才能显示:

export class App extends PureComponent {
    render() {
        return (
            <div>
                <h1>父组件App</h1>
                {/* 没登陆的话不展示,只有登录了才展示 */}
                <HOC />
                <HOC2 />
            </div>
        )
    }
}

定义高阶组件,拦截要展示的组件,进行鉴权:

function loginAuth(Component) {
    return (props) => {
        if(localStorage.getItem('token')) {
            return <Component {...props}/>
        }else {
            return <h2>没有登录,{Component.name}不展示,请先登录!</h2>
        }
    }
}

export default loginAuth;

然后这两个组件导出的时候,调用一下就行了,然后把新的组件导出,新的这个组件来决定页面要展示什么东西:

export class HOC extends PureComponent {
    render() {
        return (
            <div>
                <h2>子组件1HOC</h2>
            </div>
        )
    }
}

const newHOC = loginAuth(HOC);
export default newHOC;
function HOC2(props) {
  return (
    <div>
        <h2>子组件2HOC2</h2>
    </div>
  )
}

export default loginAuth(HOC2);

结果:
在这里插入图片描述

5.高阶组件的应用4-生命周期劫持

比如我还可以在高阶组件中计算每个组件的挂载时间

function computeInterval(Component) {
    return class extends PureComponent {
        componentWillMount() {
            this.begin = Date.now();
        }

        componentDidMount() {
            this.over = Date.now();
            let interval = this.over - this.begin;
            console.log(`${Component.name}组件渲染时间为${interval}ms`)
        }

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

在这里插入图片描述

四、Portals的使用

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:

ReactDOM.createPortal(React元素,哪个容器)
import {createPortal} from 'react-dom';
export class App extends PureComponent {
    render() {
        return (
            <div>
                <h1>App渲染在root容器中</h1>
                {createPortal(<h2>我是h2我要渲染在zzy容器中</h2>, document.querySelector('#zzy'))}
            </div>
        )
    }
}

在这里插入图片描述

应用:创建一个Model组件,让组件标签中间插槽的元素全部进入model容器中,该容器是一个固定定位:

<div id="root"></div>
<div id="zzy"></div>
<div id="model"></div>
#model {
       position:fixed;
       left: 50%;
       top: 50%;
       background-color: skyblue;
       transform: translate(-50%, -50%);
     }
import {createPortal} from 'react-dom';
import Model from './Model';

export class App extends PureComponent {
    render() {
        return (
            <div>
                <h1>App渲染在root容器中</h1>
                {createPortal(<h2>我是h2我要渲染在zzy容器中</h2>, document.querySelector('#zzy'))}
                <Model>
                    <h1>数据结构</h1>
                    <h2>算法</h2>
                    <h3>冒泡排序</h3>
                </Model>
            </div>
        )
    }
}
import {createPortal} from 'react-dom';

export class Model extends PureComponent {
  render() {
    return (
      <div>
        <h1>Model</h1>
        {createPortal(this.props.children, document.querySelector('#model'))}
      </div>
    )
  }
}

在这里插入图片描述

五、Fragment的用法和短语

每次我们写结构都要包一个根div,并且这个div会在浏览器元素中显示
在这里插入图片描述

如果不想在浏览器中显示,可以引入Fragment:

import React, { PureComponent, Fragment } from 'react';
export class App extends PureComponent {
    render() {
        return (
            <Fragment>
                <h1>奥里给</h1>
                <h2>不用根</h2>
                <h3>嗷嗷嗷</h3>
            </Fragment>
        )
    }
}

在这里插入图片描述
语法糖:不用引入,直接<></>

render() {
        return (
            <>
                <h1>奥里给</h1>
                <h2>不用根</h2>
                <h3>嗷嗷嗷</h3>
            </>
        )
    }

但是这种语法糖在遍历需要绑定key的时候不能写,必须要写完整的标签才行噢~

六、React中的CSS

1.内联样式style

就是直接写行内style,要求是传入一个对象,而且属性名要以小驼峰命名,好处是可以读取state中的变量。

export class App extends PureComponent {
    constructor() {
        super();
        this.state = {
            size: 20
        }
    }

    large() {
        this.setState({
            size: this.state.size + 5
        })
    }
    render() {
        let {size} = this.state;
        return (
            <div>
                <h1 style={{color:'red', fontSize: '30px'}}>奥里给</h1>
                <h2 style={{color:'skyblue', fontSize:`${size}px`}}>样式</h2>
                <button onClick={() => this.large()}>增大上面的字体</button>
            </div>
        )
    }
}

2.CSS模块化编写方式

如果我们把每个组件的css分别定义相应的文件然后引入,那么也是没用的,所有的css文件第一次引入后都会默认是全局css,不管写哪个文件夹里,都是共享类名,如果后续还有其他css文件包含相同类名引入,同类名样式会被下一个渲染的依次覆盖。

但是我们组件开发肯定是希望每个组件有自己的样式,这一点Vue做的很好,可以加个scope,但是React咋办呢?

这里可以用css模块化的编写方案:
第一步:名字前面加.module

在这里插入图片描述

.box {
    border: 1px solid red;
}

第二步:需要用这里样式的组件中引入:

import appStyle from './App.module.css'

第三步:使用.属性名来读取我们对应的类名

{/* 2.CSS模块化*/}
<div className={appStyle.box}>
    <h1>嗷嗷嗷</h1>
</div>

真的是非常麻烦啊,不过好像用的人还挺多的。

3.less的使用方案

参考Ant Design关于安装craco和less的配置

安装carco(create-react-app config缩写)和craco-less

npm install craco craco-less

修改package.json

/* package.json */
"scripts": {
-   "start": "react-scripts start",
-   "build": "react-scripts build",
-   "test": "react-scripts test",
	👇👇👇👇👇👇
+   "start": "craco start",
+   "build": "craco build",
+   "test": "craco test",
}

然后在项目根目录创建一个 craco.config.js 用于修改默认配置。

//craco.config.js文件
const CracoLessPlugin = require('craco-less')

module.exports = {
  plugins: [
    {
      plugin: CracoLessPlugin,
    }
  ]
}

ok,现在就可以在React中定义less并引入使用了

4.scss的使用方案

react中使用scss:

npm add node-sass@npm:dart-sass

5.CSS in JS

安装第三方库styled-components

npm i styled-components

(1)ES6模板字符串调用函数

我们先补充一个知识,就是模板字符串竟然也能tmd调用函数,参数的构成是字符串先构成一个数组作为第一个参数,然后后面的参数是引用的nameage的值

function fun(...arg) {
    console.log(arg);
}
let name = 'zzy';
let age = 18;
fun`我叫${name},我今年${age}`;

在这里插入图片描述

(2)使用styled-components的步骤

为了方便演示,我们定义一个组件,写上类名

export class App extends PureComponent {
    render() {
        return (
            <div>
                <h1 className='nav'>导航栏</h1>
                <div className='box'>
                    <h2 className='title'>标题</h2>
                    <p className='content'>内容内容</p>
                </div>
            </div>
        )
    }
}

定义一个style.js文件,里面写:

import styled from 'styled-components';

const NiuBi = styled.div`
    .nav {
        color: red;
    }

    .box {
        border: 1px solid black;

        .title {
            font-size: 40px;
            &:hover {
                background-color: blue;
            }
        }

        .content {
            color: skyblue;
        }
    }
`

export default NiuBi;

是的你没看错,这样就借助这个styled-components的库创建了一个作为div并带着样式的NiuBi组件。

紧接着我们引入NiuBi,让他作为根节点包住元素,这样的话,NiuBi里面的所有样式,我们都可以在style.js中去写,而且不会和其他地方有冲突。

import NiuBi from './style';

export class App extends PureComponent {
    render() {
        return (
            <NiuBi>
                <h1 className='nav'>导航栏</h1>
                <div className='box'>
                    <h2 className='title'>标题</h2>
                    <p className='content'>内容内容</p>
                </div>
            </NiuBi>
        )
    }
}

补充:为了写css方便,可以安装vscode的一个插件:

在这里插入图片描述

(3)模板字符串props接收数据

还有一个没见过的操作,css里因为用的是模板字符串,我们可以通过${}去读取变量,由于我们这个库返回的这个NiuBi是一个组件,那么我们可以通过组件标签给它传一些数据,读取的方式就是一个立即调用的箭头函数props => props.color

在这里插入图片描述

(4)attrs设置属性默认值

写法如下:参数是一个回调,回调的参数是props。可以给一些属性设置默认值,如果传了就用传的值,如果没传就用默认值。
在这里插入图片描述

(5)styled-components高级特性

1、支持样式的继承:

在这里插入图片描述

2、可以设置主题,然后下面的地方如果用到styled-components包裹就可以通过props.theme去读取主题的样式:

在这里插入图片描述

6.React中动态添加class

在这里插入图片描述
那么在React中怎么办呢?我们可以:

在这里插入图片描述

但是这样也比较繁琐,所以我们可以借助一个第三方库:classnames

1、安装:

npm install classnames

2、导入:

import classNames from 'classnames';

3、使用:

<h2 className={classNames('title',{active:true})}>标题</h2>

更多使用方式:

在这里插入图片描述
官方文档连接:https://github.com/JedWatson/classnames

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值