zoukankan      html  css  js  c++  java
  • 从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key

    壹 ❀ 引

    从零开始的react入门教程(三),了解react事件与使用注意项一文中,我们了解了react中事件命名规则,绑定事件时对于this的处理,以及事件中可使用的e对象。那么这篇文章中我们来熟悉react中常用的条件渲染语法。

    贰 ❀ 条件渲染

    在开发中,我们常有根据一个变量值的真或假来决定渲染A或者B内容的情况,这种需求不管用三元或者if语句都能轻松实现,比如实现一个简单的登录是否成功的文案提示功能:

    class IsLogin extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = {
                isLogin: false
            }
        }
        handleLogin = () => {
            this.setState({ isLogin: true });
        }
        handleLogout = () => {
            this.setState({ isLogin: false });
        }
        renderLogin = () => {
            return <h1>欢迎回来!</h1>
        }
        renderLogout = () => {
            return <h1>你好,请登录!</h1>
        }
        render() {
            return (
                <div className="isLogin">
                    <div>{this.state.isLogin ? this.renderLogin() : this.renderLogout()}</div>
                    <button className="login" onClick={this.handleLogin}>login</button>
                    <button className="logout" onClick={this.handleLogout}>logout</button>
                </div>
            );
        }
    };
    
    ReactDOM.render(<IsLogin />, document.getElementById('root'));
    

    在这个例子中,我们可以通过两个按钮改变state中关于isLogin的值,而这个值又决定了最终在render中应该渲染哪个文案。通过isLogin的变化,我们甚至都不需要同时显示两个按钮,同样通过变量的变化来决定渲染哪个按钮,修改render为如下,那么效果就是这样了:

    render() {
        const buttonType = this.state.isLogin
            ? <button className="logout" onClick={this.handleLogout}>logout</button>
            : <button className="login" onClick={this.handleLogin}>login</button>
        return (
            <div className="isLogin">
                <div>{this.state.isLogin ? this.renderLogin() : this.renderLogout()}</div>
                {buttonType}
            </div>
        );
    }
    

    在react的花括号中,我们还能用JS逻辑运算符玩一些花样,比如前面的例子是根据情况显示A或者B,现在我们希望要么显示A,要么什么都不显示,这里就可以用逻辑运算符&&,其实效果与满足if条件完全一致,比如这个官网提供的例子:

    function UnreadMessage(props) {
        const unreadMessage = props.msg;
        return (
            <div className="unreadMessage">
                <h1>你好!</h1>
                {unreadMessage.length > 0 &&
                    <h2>
                        你有{unreadMessage.length}条未读信息。
                    </h2>
                }
            </div>
        )
    }
    const emailMsg = [1, 2, 3];
    ReactDOM.render(<UnreadMessage msg={emailMsg} />, document.getElementById('root'));
    

    在这个例子中就凸显出了JSX语法的特点,我们将JS的逻辑判断与react元素糅合在了一起,并由{}去提供解析。站在JS的角度,这里所做的其实就是下面这段代码:

    const emailMsg = [1, 2, 3];
    let unreadMessageText = '';
    if(unreadMessage.length){
        unreadMessageText = `你有${emailMsg.length}条未读信息。`
    }
    

    逻辑运算符除了&&之外还有||,这两个的区别简单介绍下,首先是A&&B,它的执行为当执行到A为真时才会继续执行到后面的B,看个例子:

    function A(bool) {
        return bool;
    };
    function B() {
        console.log(1);
    };
    A(true) && B();//B输出1
    A(false) && B();//B不会执行
    

    A||B的意思是当A为真时就不会继续执行B了,因为只要有一个为真就可以了,所以当A为假时才会跑后面的B。

    A(true) || B();//B不会执行
    A(false) || B();//B输出1
    

    对于||的场景,比较常见的是程序中需要传递某个值,假设前者为假,我们会提供一个默认值传递,保证后续的逻辑不会出错:

    const arr = props.arr || [];
    arr.filter(ele => ele);
    

    在前面的例子中,我们都是根据条件决定组件内部渲染什么,还有种可能性,就是在特定情况下,我们希望组件内部什么都不要渲染;虽然这个组件有被调用,但不管是函数组件还是class组件,都需要通过return来返回需要渲染的react元素,所以在特定条件下,我们可以在return元素前直接return null来达到目的。

    function IsShow(props) {
        if(!props.show){
            return null;
        }
        return (
            <div >hello!</div>
        )
    }
    
    ReactDOM.render(<IsShow show={false}/>, document.getElementById('root'));
    

    这个例子中,虽然组件IsShow有被调用,但因为组件并未返回任何dom,所以在界面上我们看不到任何东西。

    那么到这里我们介绍了react中一些常见的条件渲染场景,在{}中你可以根据需要任何组合这些条件并拿到自己想要的最终结果。

    叁 ❀ 列表渲染

    在实际开发中,我们常有将数组类数据渲染成列表的需求,在vue或者或者小程序中我们可以借用指令来达到目的,比如vue中的v-for,小程序中的wx:forangularjs中的的ng-repeat等,以vue为例遍历一个数组可以这样做:

    const app = new Vue({
      el: '#list',
      data: {
        users: [
          { name: '听风' },
          { name: '是风' }
        ]
      }
    });
    
    <ul id="list">
      <!-- 利用v-for遍历 -->
      <li v-for="user in users" :key="user.name">
        {{ user.name }}
      </li>
    </ul>
    

    但我们在react中的列表渲染会有所不同,我们不会借用类似的指令,而是通过数组API直接遍历数据并得到我们想要的react元素块,再加入render中进行解析渲染。

    在JS中我们想要将一个数组中所有的元素都乘以2可以这么做:

    const doubled = [1,2,3].map(ele => ele*2);// [2, 4, 6]
    

    而react遍历列表也类似如此,比如我们需要在ul中通过li展示上面这些结果,我们则需要将要展示的所有li都提前遍历出来,再作为一个变量赋予给ul,像这样:

    function List(props) {
        const list = props.nums.map(ele => (
            <li>{ele * 2}</li>
        ));
        return <ul>{list}</ul>
    }
    const nums = [1, 2, 3];
    ReactDOM.render(<List nums={nums} />, document.getElementById('root'));
    

    在这个例子中,我们先通过map遍历,得到了包含多个li标签的合集,并保存在了变量list中,之后又将list赋予给ul标签内部,从而实现了我们想要的效果。看似完美的效果,当打开控制台就不那么完美了,这段代码报给出了红色警告:

    list中的每个child都应该有一个独一无二的属性作为key。这个问题我想大家在vue或者小程序中都有类似的处理,我们来看看react如何解决。

    肆 ❀ 独一无二的key

    肆 ❀ 壹 为什么要用key

    为什么要添加key?我想大家应该都有听说diff算法,对于react而言,每次的props或者state修改都会触发render重新渲染视图,如果是完整的重新渲染代价是昂贵的,而添加key的目的是便于react在数据修改后,能记录元素知道它对应的是先前的谁并进行对比,比如我们有个数组[0,1,2]被渲染,之后被修改为[0,2,2],对于react而言,它只要找到第二个li并修改它的渲染内容即可,而不是完整去渲染。

    所以回到上面的列表渲染的例子,我们可以这样为li添加key属性:

    const list = props.nums.map(ele => (
        <li key={ele}>{ele * 2}</li>
    ));
    

    我们直接将数组遍历的每个元素自身作为key赋予给了li,保存代码,你会发现控制台的警告已经没有了。

    肆 ❀ 贰 不推荐使用index作为key

    你也许在想,为什么不用index作为key呢?像这样:

    const list = props.nums.map((ele, index) => (
        <li key={index}>{ele * 2}</li>
    ));
    

    但用index做为key其实是有风险的,我们来看个由官网改写的例子:

    class Item extends React.Component {
        render() {
            return (
                <div>
                    <label>{this.props.name}</label>
                    <div>
                        <input type='text' />
                    </div>
                </div>
            )
        }
    }
    
    class Example extends React.Component {
        constructor() {
            super();
            this.state = {
                list: [
                    { name: '听风是风', id: 1 },
                    { name: '行星飞行', id: 2 }
                ]
            };
        }
    
        addItem = () => {
            const id = +new Date;
            this.setState({
                list: [{ name: '时间跳跃' + id + id, id }, ...this.state.list]
            });
        }
    
        render() {
            return (
                <div className="example">
                    <button onClick={this.addItem}>clie me</button>
                    <div className="form">
                        <form>
                            <h3>不好的做法 <code>key=index</code></h3>
                            {this.state.list.map((todo, index) =>
                                <Item {...todo}
                                    key={index} />
                            )}
                        </form>
                        <form>
                            <h3>更好的做法 <code>key=id</code></h3>
                            {this.state.list.map((todo) =>
                                <Item {...todo}
                                    key={todo.id} />
                            )}
                        </form>
                    </div>
    
                </div>
            )
        }
    }
    ReactDOM.render(<Example />, document.getElementById('root'))
    

    当我们提前为input输入了值,并点击按钮新建输入框时效果就很明显了,我们的本意是在现有输入框头部插入新的输入框。但当使用inde作为key时react对比了新旧index为0的input,由于index前后都是0,所以react认为此时的item组件是可以复用的,它并没有完全替换掉它,而是单纯更新了item内部的label标签,所以你会发现input创建出来是有值的。

    而当我们使用第一无二的标识作为key时点击创建,由于前后根本不是一个东西,react选择了重新创建一个全新的lable与input,并插入到了现有DOM节点之前。

    通常来说,我们始终不推荐使用index作为key,更好的做法是为需要遍历的数据都提供独一无二的id,常规来说后端返回的数据都会满足这一点。

    但如果你说我的数据就是没id,这可怎么办,在react官网介绍的博客中,也推荐了用于随机生成id的小工具,例如shortidshortid或者Nano ID,有兴趣大家可以自己看看用法。

    肆 ❀ key与组件

    在上一个介绍index作为key会造成问题的例子中,不知道大家有没有发现key是写在需要遍历的组件Item上,而非item内部的div上,其实不难理解,对于react而言,组件Item就是一个整体,我们希望这个整体带有唯一标识,在数据变化时,当前的Item是否应该更新或是新建,所以下面这样的写法就是错误的:

    function ListItem(props) {
        const value = props.value;
        return (
            // 错误!你不需要在这里指定 key:
            <li key={value.toString()}>
                {value}
            </li>
        );
    }
    
    function List(props) {
        const listItems = [1, 2, 3].map((number) =>
            // 错误!元素的 key 应该在这里指定:
            <ListItem value={number} />
        );
        return (
            <ul>
                {listItems}
            </ul>
        );
    }
    
    ReactDOM.render(<List />, document.getElementById('root'))
    

    一个规则就是,key永远加在你所用的数组API内部的元素上,修改成如下这样就好了:

    function ListItem(props) {
        const value = props.value;
        return (
            <li>
                {value}
            </li>
        );
    }
    
    function List(props) {
        const listItems = [1, 2, 3].map((number) =>
            <ListItem value={number}  key={number.toString()}/>
        );
        return (
            <ul>
                {listItems}
            </ul>
        );
    }
    

    关于key最后一点说明就是,虽然我们说key应该独一无二,但并不是说它在全局是独一无二,而是只针对于兄弟元素之前,在我们前面展示index作为key的例子中,其实我们也将数组给了form中去遍历,由于不是兄弟关系,你会发现它们之间的key就算重名也没任何关系。

    伍 ❀ 总

    好了,那么到这里我们介绍了react中几种常见的条件渲染用法,其实总结来说,在react的{}中我们能做到很多JS中的条件判断骚操作。

    除了条件渲染,我们还介绍了列表渲染,这才开发中将非常普遍,与常规框架不同,react并未提供对应的指令,而是借用数组API直接渲染react元素,而说到列表渲染总是离不开与之配对的key,我们了解了为什么要提供key,以及使用index作为key可能造成的问题,所以在开发中总是建议不要使用index作为key。以上知识就是本文阐述的几个核心点了,时间也不早了,那么到这里本文结束,晚安。

  • 相关阅读:
    WPS客户端更新日志留着备用
    C#设置电脑时间帮助类
    分布式数据库拆表拆库的常用策略
    程序员如何对待自己的工作
    java对接申通下单接口示例代码
    电子面单开发流程
    2016年工作总结和计划
    【价值观】大牛们的经典语录
    大话 程序猿 眼里的 接口
    【C#公共帮助类】DateTimeHelper设置电脑本地时间,实际开发很需要
  • 原文地址:https://www.cnblogs.com/echolun/p/14100533.html
Copyright © 2011-2022 走看看