React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同

React 事件的命名采用小驼峰式(camelCase),而不是纯小写

使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串

一、事件处理

1.事件绑定

React 元素的事件处理和 DOM 元素类似,但是在语法上有些区别,比如:

传统的html:用双引号包裹,后面必须跟参数

<button onclick="myfun()">点击</button>

React:用大括号包裹,后面不跟参数

<button onclick={myfun}>点击</button>

一个完整的事件函数代码如下

class Demo extends React.Component {
    render() {

        // 事件函数
        function myfun() {
            alert('helo,world')
        }

        return (
            // 绑定事件
            <button onClick={this.myfun}>
                Activate Lasers
            </button>
        )
    }
}

ReactDOM.render(
    <Demo />,
    document.getElementById('root')
)

如果方法后面没有(),则需要为这个方法绑定 this

2.阻止默认行为

在 React 中还有一个不同的点,不能通过返回 fasle 阻止默认行为, React 提供了一个属性--preventDefault,可以通过 preventDefault 阻止脚本执行

看一下传统的 html 和 React 的对比

<a href="#" onclick="alert('是否弹窗?');return false">
    Click me
</a>

直接在写上 false 就可以阻止脚本执行

React 通过 preventDefault 属性阻止脚本执行:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

二、条件渲染

在 React 中,可以创建不同的组件来封装各种你需要的行为,然后,根据应用不同的状态,你可以只渲染对应状态下的部分内容。

React 中的条件渲染和 javascript 中的一样,使用 if 运算符来表示元素当前的状态,然后让 React 根据他们来更新 UI。

使用 if..else 语句进行条件渲染

先写一个条件渲染的例子,定义了两个组件,然后通过判断组件 Greeting 中的变量 isLoggedIn 的真伪,让浏览器渲染组件 UserGreeting 或者 GuestGreeting

// App.js
import React, { Component } from 'react'

export default class App extends Component {
  render() {
    function UserGreeting(props) {
      return <h3>Welcome back!</h3>;
    }

    function GuestGreeting(props) {
      return <h3>Please sign up.</h3>;
    }

    function Greeting(props) {
      const isLoggedIn = props.isLoggedIn;
      if (isLoggedIn) {
        return <UserGreeting />;
      }   
      return <GuestGreeting />;
    }

    return (
      <div>
        <Greeting isLoggedIn={false} />
      </div>
    )
  }
}

最后变量 isLoggedIn 定义了 false,因此,浏览器渲染 `GuestGreeting。

怎么阻止条件渲染?

在有些情况下,我们希望能隐藏组件,即使他已经被其他组件渲染。我们可以通过 render 方法返回 null 让组件不渲染。

下面的示例中,<WarningBanner /> 会根据 prop 中 warn 的值来进行条件渲染。如果 warn 的值是 false,那么组件则不会渲染:

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(state => ({
      showWarning: !state.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

三、渲染列表

先看一段代码,我们使用 map() 函数让数组中的每一项变双倍,然后得到一个新的数组 doubled 并打印出来。

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

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

而在 React 中,把数组转换为元素列表的过程是相似的。

先通过 map() 方法遍历 numbers 数组,将数组中的每个元素变成 <li> 标签,最后将得到的数组赋值给 listItems

然后返回 {listItem}

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

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

运行之后浏览器出现 1-5 的无序列表

1.分离组件

上面就是一个基本的列表渲染的例子,但是数据写死了。接下来我们将数组重构成一个组件,以后再进行数组渲染时,可以轻松调用。

// Map.js
export default class Map extends Component {
  render() {

  // 分离出组件 NumberList 作为转换数组的组件
    function 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, 6, 7];

    return (
      <div>
        <NumberList numbers={numbers} />
      </div>
    )
  }
}

2.key

运行代码之后,页面会正常显示,但是控制台会报一个错误。Each child in a list should have a unique "key" prop.,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。

现在给每个列表元素分配一个key:

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

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

3.使用 id 作为 key

key 帮助 React 识别了哪些元素被改变,比如删除和添加,所以应当给每个元素确定一个标识,也就是 key

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key

// Map.js
export default class Map extends Component {
  render() {

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li key={number.id}>  // 赋值 key
          {number.text}
        </li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }

    // 传入数据
    const numbers = [
      {id: 1,text: 1},
      {id: 2,text: 2},
      {id: 3,text: 3},
      {id: 4,text: 4},
      {id: 5,text: 5}
    ];

    return (
      <Fragment>
        <NumberList numbers={numbers} />
      </Fragment>
    )
  }
}

4.索引 index 可以作为 key 吗?

当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key

const todoItems = todos.map((todo, index) =>
  // 仅仅当没有确定 id 的时候使用索引index作为 key
  <li key={index}>
    {todo.text}
  </li>
);

如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。

5.用 key 提取组件

比方说,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 <ListItem /> 元素上,而不是放在 ListItem 组件中的 <li> 元素上。

<red>错误的使用方法:</red>

function ListItem(props) {
  const value = props.value;
  return (
    // 错误!你不需要在这里指定 key:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 错误!元素的 key 应该在这里指定:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

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

<green>正确的使用方法:</green>

function ListItem(props) {
  // 正确!这里不需要指定 key:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正确!key 应该在数组的上下文中被指定
    <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')
);

React:一个好的经验法则是:在 map( ) 方法中的元素需要设置 key 属性。

6.key 只是在兄弟节点之间必须唯一

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

function 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')
);

7.vue 中渲染列表

Vue 中渲染列表使用的是特殊指令 v-for,其中也有 key 的相关用法

React 中采用的是 map() 方法遍历数组,然后渲染列表