React入门 Part4

列表和Keys

在React中使用列表,应该为列表中的每一个元素添加一个名为key的属性(不添加也可以,但是会有提示建议添加)。

当列表数据发生变化时,React就可以通过key知道哪些元素发生了变化,从而只重新加载发生变化的元素,以提高渲染效率。

可以使用元素的索引(index)作为元素的key值,但是这样造成的问题是,如果元素发生了重排则会导致元素的索引发生变化,这样不利于React的渲染优化。

示例代码:

import React from 'react'
import PostItem from '../p3/PostItem'

class PostList extends React.Component {

    constructor(props) {
        super(props);
        //数据
        this.state = {
            posts:[]
        };
        //定时器
        this.timer = null;
        //指定点击事件
        this.handleVote = this.handleVote.bind(this);
    }

    //初次渲染结束
    componentDidMount() {
        //异步模拟从服务端请求数据
        this.timer = setTimeout(() => {
            this.setState({
                posts: [
                    {id: 1, title: "帖子1", author: "张三", date: "2021-02-15 11:11:11", vote: 0},
                    {id: 2, title: "帖子2", author: "李四", date: "2021-02-15 12:11:11", vote: 0},
                    {id: 3, title: "帖子3", author: "王五", date: "2021-02-15 13:11:11", vote: 0},
                    {id: 4, title: "帖子4", author: "赵六", date: "2021-02-15 14:11:11", vote: 0}
                ]});
        }, 1000);
    }

    //组件卸载之前
    componentWillUnmount() {
        if(this.timer) {
            //清除计时器
            clearTimeout(this.timer);
        }
    }

    //处理点击
    handleVote(id) {
        //找到目标item
        const newPosts = this.state.posts.map(item => {
            const newItem = item.id === id ? {...item, vote: ++item.vote} : item;
            return newItem;
        });
        //更新state中数据
        this.setState({
            posts: newPosts
        });
    }

    render() {
        return (
            <div>
                帖子列表:
                <ul>
                    {
                        this.state.posts.map(item => 
                            <PostItem post = {item} onVote= {this.handleVote} key ={item.id}/>
                        )
                    }
                </ul>
            </div>
        );
    }
}

export default PostList;

事件处理

在React元素中绑定事件有两个注意点:

  • 在React中,事件的命名采用驼峰命名方式,而不是DOM元素中的小写字母命名方式;
  • 处理事件的响应函数要以对象的形式赋值给事件属性,而不是DOM中的以字符串形式;

示例代码:

<button οnclick={clickBtn}>
    按钮
</button>

React中的事件是合成事件,并不是原生的DOM事件。

在DOM事件中,可以通过处理函数返回的false来阻止事件的默认行为,但在React事件中,必须显式地调用事件对象的preventDefault方法来阻止事件的默认行为,除了这一点DOM事件和React事件在使用上并无差异。

默认行为

JavaScript事件,例如,a标签的跳转,Submit按钮的提交,右键菜单,文本框的输入等。

在React事件中,最容易出现问题的是事件处理函数中this的指向问题,因为ES 6 class并不会为方法自动绑定this到当前对象。

React事件处理函数的写法主要有以下三种:

  1. 使用箭头函数
  2. 使用组件方法
  3. 属性初始化语法

使用箭头函数进行处理函数的定义

箭头函数中的this指向的是函数定义时的对象,所以可以保证this总是指向当前组件的实例对象。

直接在render方法中为元素事件定义事件处理函数,最大的问题是,每次render调用时,都会重新创建一个新的事件处理函数,带来额外的性能开销,组件所处层级越低,这种开销就越大,因为任意一个上层组件的变化都有可能会触发这个组件的render方法。

import React from 'react'

class ArrowCreateEventComponent extends React.Component {
    
    constructor(props) {
        super(props);
        this.state = {number: 0};
    }

    //处理点击事件
    handleClick(event) {
        let newNumber = this.state.number + 1;
        this.setState({
            number: newNumber
        });
    }

    render() {
        return (
            <div>
                <div>{this.state.number}</div>
                <button onClick={(event) => {this.handleClick(event);}}>
                    点击
                </button>
            </div>
        );
    }
}
export default ArrowCreateEventComponent;

使用组件方法进行处理函数的定义

直接将组件的方法赋值给元素的事件属性,同时在类的构造函数中,将这个方法的this绑定到当前对象。

这种方式的好处是,每次render不会重新创建一个回调函数,没有额外的性能损失。但在构造函数中,为事件处理函数绑定this,尤其是存在多个事件处理函数需要绑定时,这种模板式的代码显得比较繁琐。

示例代码:

import React from 'react'

class MyComponent extends React.Component {
    
    constructor(props) {
        super(props);
        this.state = {number: 0};
        //绑定处理事件
        this.handleClick = this.handleClick.bind(this);
    }

    //处理点击事件
    handleClick(event) {
        let newNumber = this.state.number + 1;
        this.setState({
            number: newNumber
        });
    }

    render() {
        return (
            <div>
                <div>{this.state.number}</div>
                <button onClick={this.handleClick}> 按钮 </button>
            </div>
        );
    }
}

export default MyComponent;

如果需要为处理函数传递额外的参数:

import React from 'react'

class MyComponent extends React.Component {
    
    constructor(props) {
        super(props);
        //声明初始数据
        this.state = {
            list: [1,2,3,4],
            current: 1
        };
    }

    //处理点击事件
    handleClick(item, event) {
        this.setState({
            current: item
        });
        console.log("current:" + item);
    }

    render() {
        return (
            <ul>
                {
                    this.state.list.map(
                        (item) => (
                            <li className={this.state.current === item ? 'current' : ''}
                                key={item}
                                //传入参数
                                onClick={this.handleClick.bind(this, item)}>
                                {item}
                            </li>
                        )
                    )
                }
            </ul>
        );
    }
}

export default MyComponent;

使用属性初始化语法进行处理函数的定义

使用ES 7的property initializer 会自动为class总定义的方式绑定this。

示例代码:

import React from 'react'

class MyComponent extends React.Component {
    
    constructor(props) {
        super(props);
        this.state = {number: 0};
    }

    handleClick = (event) => {
        let newNumber = this.state.number + 1;
        this.setState({
            number: newNumber
        });
    }

    render() {
        return (
            <div>
                <div>{this.state.number}</div>
                <button onClick={this.handleClick}> 按钮 </button>
            </div>
        );
    }
}

export default MyComponent;

表单

像div、p、span等非表单元素只需根据组件的属性或者状态进行渲染即可,但表单元素自身维护一些状态,而这些状态默认情况下是不受React控制的,我们称不受React控制的表单元素为非受控组件。

在React中,状态的修改必须通过组件的state,非受控组件的行为显然是有悖于这一原则的。

为了让表单元素状态的变更也能通过组件的state管理,React采用受控组件的技术来达到这一目的。

受控组件

如果一个表单元素的值是由React来管理的,那么它就是一个受控组件。

React组件渲染表单元素,并在用户和表单元素发生交互时控制表单元素的行为,从而保证组件的state成为界面上所有元素状态的唯一来源。

React对不同的表单元素有不同的控制方式。

文本框

文本框包含类型为text的input元素和textarea元素。

受控的原理是:通过表单元素的value属性设置表单元素的值,通过表单元素的onChange事件监听值的变化,并将变化同步到React组件的state中。

示例代码:

import React from 'react'

class LoginForm extends React.Component {

    //构造方法
    constructor(props) {
        super(props);
        //初始状态
        this.state = {
            name: '',
            password: ''
        };
        //关联处理事件
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    //监听用户名和密码的改变
    handleChange(event) {
        const target = event.target;
        this.setState({
            [target.name]: target.value
        });
    }

    //表单提交的响应函数
    handleSubmit(event) {
        console.log('用户登录成功,当前state中的name:' + this.state.name + ',password:' + this.state.password);
        event.preventDefault();
    }

    render() {
        return(
            <form onSubmit={this.handleSubmit}>
                <label>
                    用户名:
                    <input type="text" name="name" value={this.state.name} onChange={this.handleChange}/>
                </label>
                <label>
                    密码:
                    <input type="password" name="password" value={this.state.password} onChange={this.handleChange}/>
                </label>
                <input type="submit" value="登录"/>
            </form>
        );
    }
}

export default LoginForm;

当前用户更改表单元素的值时,onChange事件会被触发,对应的handleChange处理函数会把变化同步到组件的state,新的state又会触发表单元素的重新渲染,从而实现对表单元素状态的控制。

技巧:

为两个input元素分别指定name属性,使用同一个函数handleChange处理元素值的变化,在处理函数中根据元素的name属性来区别事件的来源。

列表

列表select元素是复杂的表单元素,它可以用来创建一个下拉列表。

在React中,通过在select上定义value属性来决定哪一个选项(option)元素处于选中状态。

这样,对select的控制只需要在select这一个元素上修改即可,而不需要关注option元素。

示例代码:

import React from 'react'

class ReactStackForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value: 'apple'}
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    //监听下拉列表的变化
    handleChange(event) {
        this.setState({
            value: event.target.value
        });
    }

    //表单提交响应函数
    handleSubmit(event) {
        console.log('提交数据:' + this.state.value);
        event.preventDefault();
    }

    render() {
        return(
            <form onSubmit={this.handleSubmit}>
                <label>
                    选择一个选项:
                    <select value={this.state.value} onChange={this.handleChange}>
                        <option value="apple">apple</option>
                        <option value="react">react</option>
                        <option value="redux">redux</option>
                        <option value="saga">saga</option>
                    </select>
                </label>
                <input type="submit" value="Submit"/>
            </form>
        );
    }
}

export default ReactStackForm;

复选框和单选框

复选框是类型为checkbox的input元素,单选框是类型为radio的input元素,它们的受控方式不同于类型为text的input元素。

通常,复选框和单选框的值是不变的,需要改变的是它们的checked状态,因此React控制的属性不再是value属性,而是selected属性。

示例代码:

import React from 'react'

class ReactStackFormP extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            apple: false,
            react: false,
            redux: false,
            saga: true //默认选择saga
        }
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    //监听列表的变化
    handleChange(event) {
        this.setState({
            [event.target.name]: event.target.checked
        });
    }

    //表单提交响应函数
    handleSubmit(event){
        event.preventDefault();
    }

    render() {
        return(
            <form onSubmit={this.handleSubmit}>
                <label>
                    apple
                    <input type="checkbox" name="apple" checked={this.state.apple} onChange={this.handleChange}/>
                </label>
                <label>
                    react
                    <input type="checkbox" name="react" checked={this.state.react} onChange={this.handleChange}/>
                </label>
                <label>
                    redux
                    <input type="checkbox" name="redux" checked={this.state.redux} onChange={this.handleChange}/>
                </label>
                <label>
                    saga
                    <input type="checkbox" name="saga" checked={this.state.saga} onChange={this.handleChange}/>
                </label>
                <input type="submit" value="Submit"/>
            </form>
        );
    }
}

export default ReactStackFormP;

非受控组件

使用受控组件虽然保证了表单元素的状态也由React统一管理,但需要为每个表单元素定义onChange事件的处理函数,然后把表单状态的更改同步到React组件的state。

非受控组件指表单元素的状态依然由表单元素自己管理,而不是交给React组件管理。

使用非受控组件需要有一种方式可以获取到表单元素的值,React中提供了一个特殊的属性ref,来引用React组件或DOM元素的实例,因此可以通过为表单元素定义ref属性来获取元素的值。

示例代码:

import React from 'react'

class SimpleForm extends React.Component {
    constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleSubmit(event) {
        alert('你输入了:' + this.input.value);
        event.preventDefault();
    }

    render() {
        return(
            <form onSubmit={this.handleSubmit}>
                <label>
                    <input type="text" ref={(input) => this.input = input}/>
                </label>
                <input type="submit" value="提交"/>
            </form>
        );
    }
}

export default SimpleForm;

ref的值是个函数,这个函数会接收当前元素作为参数。

在使用非受控组件时,通常需要为相应的表单元素设置默认值,但是无法通过表单元素的value属性设置,因为非受控组件中,React无法控制表单元素的value属性,这也就意味着一旦在非受控组件中定义了value属性的值,就很难保证后续表单元素值的正确性,此种情况可以使用defaultValue属性来指定默认值。

示例代码:

import React from 'react'

class SimpleForm extends React.Component {
    constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleSubmit(event) {
        alert('你输入了:' + this.input.value);
        event.preventDefault();
    }

    render() {
        return(
            <form onSubmit={this.handleSubmit}>
                <label>
                    <input type="text" ref={(input) => this.input = input} defaultValue="这是默认值"/>
                </label>
                <input type="submit" value="提交"/>
            </form>
        );
    }
}

export default SimpleForm;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值