list 列表和 key

在 JavaScript 中我们通常使用 map method 来对一个 list 的每个元素进行操作:

const numbers = [1, 2, 3, 4, 5];
const double = numbers.map((number) => { return number * 2});
console.log(double)

//output:
//[ 2, 4, 6, 8, 10 ]

在 React 中对一个 list 的元素进行操作方法类似。

我们可以在 JSX 中通过大括号{} 来建立一个 elements 的集合,下面示例中我们将 map 的返回定义为 <li> 元素并赋值给 listItems:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li>{number}</li>)

ReactDOM.render(
    <ul>{listItems}</ul>,
    document.getElementById('root')
);

注意在 render 中我们将 listItems 放在 <ul> 元素中。

通常情况下我们将 lists 放在一个 component 中:

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) => <li>{number}</li>)
    return (
        <ul>{listItems}</ul>
    )
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers}/>,
    document.getElementById('root')
);

当运行以上代码时,在浏览器终端会有一个 warning 警告信息:Each child in a list should have a unique "key" prop.
2021-03-05T06:50:19.png

Key 是一个特殊的 string 字符串属性需要给创建的 list element 添加的。它可以用来定位 list 中的每个元素。

下面我们给 list item 添加 Key 字符串属性:

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
        <li key={number.toString()}>
            {number}
        </li>);
    return (
        <ul>{listItems}</ul>
    );
}

添加后报警就会消除。

Keys

Key 可以帮助 React 识别哪个 item 修改过,被删除,被添加。以上示例中,我们在 map 中创建 item 时给其 key 属性,这样每个 item 可以有确切的属性值。

每个 list item 最好设置一个特殊的标识 key string 来区别于其他 items。最常用的就是使用数据中的 ID 作为 key:

const TodoItems = (props) => {
    const todos = props.todos;
    const listItems = todos.map((todo) => 
        <li key={todo.id}>
            {todo.text}
        </li>
    )
    return (
        <ul>{listItems}</ul>
    );
}
const todos = [
    {id: 1, text: '123'},
    {id: 2, text: '456'}
];
ReactDOM.render(
    <TodoItems todos={todos} />,
    document.getElementById('root')
);

当没有特定的 ID 来作为标识时,作为最后的选择,可以使用 item 的 index 作为 key:

const TodoItems = (props) => {
    const todos = props.todos;
    const listItems = todos.map((todo, index) => 
        <li key={index}>
            {todo.text}
        </li>
    )
    return (
        <ul>{listItems}</ul>
    );
}

如果 items 的顺序可能会发生变化的话,不推荐使用 index 作为 key 使用,因为可能对性能产生影响并且对 component 的 state 造成问题。如果没有定义确切的 key 给 items,React 默认会使用 index 作为 keys。

拆解 component 时 key 的处理

keys 是对应与一个数组的内容而言的,它并不能单独存在。例如我们要拆解上面的 NumberList,提取出 ListItem,则需要将 key 定义在 <ListItem /> 元素中而不是 ListItem component 内部的 <li> 中:

const ListItem = (props) => {
    return (
        <li>{props.value}</li>
    );
}

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
        <ListItem key={number.toString()} value={number}/>);
    return (
        <ul>{listItems}</ul>
    );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById('root')
);

如果写成下面模式就是错误的:

function ListItem(props) {
  const value = props.value;
  return (
    <li key={value.toString()}>
      {value}
    </li>
  );
}

每个 item 的 key 必须是特定的

数组中每个 items 使用的 key 必须是互相独立且不相同的,但并不需要在全局下互相独立。在两个单独的数组中可以,其元素可以使用相同的 key:

const React = require('react')
const ReactDOM = require('react-dom')

const Blog = (props) => {
    const sideBar = (
        <ul>
            {props.posts.map((post) =>
                <li key={post.id}>{post.title}</li>
            )}
        </ul>
    );
    const content = props.posts.map((post) =>
        <div key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
        </div>
    );
    return (
        <div>
            {sideBar}
            <hr/>
            {content}
        </div>
    );
}

const posts = [
    {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
    {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];

ReactDOM.render(
    <Blog posts={posts} />,
    document.getElementById('root')
);

上面示例中,我们在 Blog component 中定义了两个 JSX,都创建了 list elements,每个元素的 key 使用了对应的 id 属性。在每个 list 内部 key 是互相独立的。可以看到不只是 <li> 元素可以加 key,只要通过 map 定义了一个 array 数组,就可以给每个元素加上 key 属性来互相独立识别。

key 是为了给 React 识别用的。它本身并不作为一个普通 prop 传给 components,也就是在 component 内部并不能使用这个 key 数据,如果想要在 component 中使用这个数据则需要单独定义一个其他 prop 来传入 key 数据:

const Post = (props) => {
    return (
        <li>
            {props.id}: {props.title}
        </li>
    )
}
const Blog = (props) => {
    const sideBar = (
        <ul>
            {props.posts.map((post) =>
                <Post key={post.id} id={post.id} title={post.title} />
            )}
        </ul>
    );
...
...
...
}

上面示例中,Post component 无法直接访问 key 的数据,所以我们在调用 Post 时单独定义一个 id 属性并赋值为 key 相同的数据,这样就间接的可以在 Post component 中通过 id 来读取 key 的数据。

在之前的 ListItem 示例中,我们声明了一个单独的 listItems 变量并在后续返回中将其放在 <ul> 中:

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
        <ListItem key={number.toString()} value={number}/>);
    return (
        <ul>{listItems}</ul>
    );
}

JSX 支持嵌入任何的 JavaScript 表达式,只需要使用大括号包围即可,所以上面的代码可以修改为以下模式:

    return (
        <ul>
            {numbers.map((number) =>
                <ListItem key={number.toString()} value={number} />);}
        </ul>
    );

使用哪种方式来定义 JSX 取决于对应的使用场景,总的原则是要方便与代码阅读,逻辑清晰。需要注意的是如果 map() method 中层级太复杂,可以考虑将其拆分为多个 components。

Forms 表格

标签: none

添加新评论