title: 列表渲染

首先,让我们看下在 Javascript 中如何转化列表:

如下代码,我们使用 map() 函数让数组中的每一项翻倍,我们得到了一个新的数列 doubled

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

代码打印出 [2, 4, 6, 8, 10]

在 Taro 中,把数组转化为数列元素的过程是相似的。

渲染多个组件

下面,我们使用 JavaScript 中的 map() 方法遍历 numbers 数组。对数组中的每个元素返回 <Text> 标签,最后我们得到一个数组 listItems

  1. const numbers = [...Array(100).keys()] // [0, 1, 2, ..., 98, 99]
  2. const listItems = numbers.map((number) => {
  3. return <Text className='li'> 我是第 {number + 1} 个数字</Text>
  4. })

这段代码生成了一个 1 到 100 的数字列表。

Keys

但是在上面的代码,你会得到一个报错:提醒你当循环一个数组时应该提供 keys。Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 Nerv/小程序 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。

  1. const numbers = [...Array(100).keys()] // [0, 1, 2, ..., 98, 99]
  2. const listItems = numbers.map((number) => {
  3. return <Text
  4. key={String(number)}
  5. className='li'
  6. >
  7. 我是第 {number + 1} 个数字
  8. </Text>
  9. })

taroKeys

taroKey 适用于循环渲染原生小程序组件,赋予每个元素唯一确定标识,转换为小程序的 wx:key

  1. const numbers = [...Array(100).keys()] // [0, 1, 2, ..., 98, 99]
  2. const listItems = numbers.map((number) => {
  3. return (
  4. // native component
  5. <g-list
  6. taroKey={String(number)}
  7. className='g-list'
  8. >
  9. 我是第 {number + 1} 个数字
  10. </g-list>
  11. )
  12. })

元素的 key 在他的兄弟元素之间应该唯一

数组元素中使用的 key 在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key:

  1. class App extends Componenet {
  2. state = {
  3. posts: [
  4. {id: 1, title: 'Hello World', content: 'Welcome to learning Taro!'},
  5. {id: 2, title: 'Installation', content: 'You can install Taro from npm.'}
  6. ]
  7. }
  8. render () {
  9. const { posts } = this.state
  10. const sidebar = (
  11. <View>
  12. {posts.map((post) =>
  13. <Text key={post.id}>
  14. {post.title}
  15. </Text>
  16. )}
  17. </View>
  18. )
  19. const content = posts.map((post) => {
  20. return <View key={post.id}>
  21. <Text>{post.title}</Text>
  22. <Text>{post.content}</Text>
  23. </View>
  24. })
  25. return (
  26. <View>
  27. {sidebar}
  28. <View className="divider" />
  29. {content}
  30. </View>
  31. )
  32. }
  33. }

key 会作为给 Taro 的提示,但不会传递给你的组件。如果您的组件中需要使用和 key 相同的值,请将其作为属性传递:

  1. const content = posts.map((post) => {
  2. return <View key={post.id} id={post.id} >
  3. <Text>{post.title}</Text>
  4. <Text>{post.content}</Text>
  5. </View>
  6. })

key 的取值

key 的取值必须同时满足三个条件:

  1. 稳定
  2. 可预测
  3. 唯一(相对于其他兄弟元素)

最好的 key 就是数组里的 ID(通常由后端生成),他能同时满足以上三个条件,同时也不需要自己去生成。如果没有 ID,你能保证数组的元素某个键值字符串都是不同的(例如 item.title),那么使用那个字符串键值也可以。如果源数据没有提供很好的 key 值,或者需要遍历的数组生成的。那么你最好在数据创建或者修改之后给他添加一个好的 key 值:

  1. let todoCounter = 0
  2. function createNewTodo(text) {
  3. return {
  4. completed: false,
  5. id: todoCounter++,
  6. text
  7. }
  8. }
  9. class App extends Components {
  10. state = {
  11. todos: [],
  12. inputText: ''
  13. }
  14. onNewTodo () {
  15. this.setState({
  16. todos: [...this.state.todos, createNewTodo(this.state.inputText)]
  17. })
  18. }
  19. render () {
  20. return ...
  21. }
  22. }

每一个在渲染结果上一致组件的应该对应一个相同的 key。因此使用数组的 index 或在数组渲染时随机生成一个 key 值(但你在创建数组时可以这么做)都是反优化,极端情况下甚至可能导致渲染出错。

与 React 的不同

在 React 中,JSX 是会编译成普通的 JS 的执行,每一个 JSX 元素,其实会通过 createElement 函数创建成一个 JavaScript 对象(React Element),因此实际上你可以这样写代码 React 也是完全能渲染的:

  1. const list = this.state.list.map(l => {
  2. if (l.selected) {
  3. return <li>{l.text}</li>
  4. }
  5. }).filter(React.isValidElement)

你甚至可以这样写:

  1. const list = this.state.list.map(l => {
  2. if (l.selected) {
  3. return {
  4. '$$typeof': Symbol(react.element),
  5. 'props': {
  6. children: l.text
  7. },
  8. 'type': 'li'
  9. }
  10. }
  11. }).filter(React.isValidElement)

但是 Taro 中,JSX 会编译成微信小程序模板字符串,因此你不能把 map 函数生成的模板当做一个数组来处理。当你需要这么做时,应该先处理需要循环的数组,再用处理好的数组来调用 map 函数。例如上例应该写成:

  1. const list = this.state.list
  2. .filter(l => l.selected)
  3. .map(l => {
  4. return <li>{l.text}</li>
  5. })