从零开始的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。以上知识就是本文阐述的几个核心点了,时间也不早了,那么到这里本文结束,晚安。

原文地址:https://www.cnblogs.com/echolun/p/14100533.html