一、Hello World

1. 最简易的 React 示例

  1. ReactDOM.render(
  2. '<h1>Hello World</h1>',
  3. document.querySelector('#root')
  4. )
  • React Apps 的构建块就是:元素和组件(elements 和 components)
  • 只要掌握了它们就可以重复使用这些块构建复杂的应用程序

二、JSX 语法

  1. const element = <h1>Hello, world!</h1>;

JSX 是 JavaScript 的语法扩展,它看起来有点像模板语言,而其实拥有 JavaScript 的全部功能

React 认为处理逻辑与 UI 本质上是耦合的

  • 如何处理事件
  • 状态如何随时间变化
  • 数据如何为展现做准备

与通过将标记和逻辑放在单独的文件中来认为将技术分离不同,React 通过称为“组件”的松散耦合的单元来分离关注点,它既包含标记,也包含逻辑。

React 不需要 JSX,但是在使用时将 UI 放入 JavaScript 代码能对视觉帮助很有用。它还允许帮助 React 显示更多有用的错误和警告消息。

1. JSX 中嵌入表达式

  1. const name = 'Josh Perez'
  2. function formatName(user) {
  3. return user.firstName + ' ' + user.lastName
  4. }
  5. const element = (<h1>Hello, {name}</h1>)
  6. const element2 = (
  7. <h1>
  8. Hello, {formatName(user)}!
  9. </h1>
  10. )
  • 可以把任何有效的 JavaScript 表达式放入 JSX 的大括号中
  • JSX 拆分成多行时,建议加上圆括号
    • 我写 JS 的习惯是不加分号,所以写 JSX 时,不管单行还是多行,都一定要加分号,否则会出错

      2. JSX 也是个表达式

      1. function getGreeting(user) {
      2. if (user) {
      3. return <h1>Hello, {formatName(user)}!</h1>;
      4. }
      5. return <h1>Hello, Stranger.</h1>;
      6. }
  • 经过编译后,JSX 表达式变成了普通的函数调用,然后计算得到 JavaScript 对象

  • 我们可以很轻松使用 if 语句、for 循环使用 JSX,它可以被赋值给变量,可以作为函数的参数,也可以作为函数的返回值

3. JSX 中指定属性

  1. const element = (<div tabIndex="0"></div>)
  2. const element = (<img src={user.avatarUrl}></img>)
  • 可以使用引号将字符串指定为属性
  • 可以使用大括号将 JavaScript 表达式嵌入到属性中
  • JSX 的语法更接近 JS 而不是 HTML,React DOM 使用驼峰属性的命名原则,而不是 HTML 属性名

4. JSX 中指定子元素

  1. const element = <img src={user.avatarUrl} />
  2. const element2 = (
  3. <div>
  4. <h1>Hello!</h1>
  5. <h2>Good to see you here.</h2>
  6. </div>
  7. )
  • 如果一个标签的内容是空的,可以像 XML 一样使用 /> 立刻关闭

5. JSX 防止注入攻击

  1. const title = response.potentiallyMaliciousInput;
  2. // This is safe:
  3. const element = <h1>{title}</h1>;
  • 默认情况下,React DOM 会渲染 JSX 内容之前转译所有嵌入其中的值,所以在应用里不会注入任何东西
  • 所有东西在渲染之前都会转换成字符串,这能防止 XSS(cross site scripting)攻击

6. JSX 表示为对象

  1. const element = (
  2. <h1 className="greeting">
  3. Hello, world!
  4. </h1>
  5. )
  6. const element2 = React.createElement(
  7. 'h1',
  8. {className: 'greeting'},
  9. 'Hello, world!'
  10. )
  • Babel 将 JSX 编译为 React.createElement() 调用,上面两个写法是等价的
  • React.createElement() 执行一些检查来帮助我们写一些没有 bug 的代码,它实际创建了这样一个对象
  • 这些对象被称为“React 元素”,React 读取它们来构造 DOM, 并让 DOM 与最新 data 保持同步更新
    1. // Note: this structure is simplified
    2. const element = {
    3. type: 'h1',
    4. props: {
    5. className: 'greeting',
    6. children: 'Hello, world!'
    7. }
    8. }

三、渲染元素

  1. const element = (<h1>Hello, world</h1>)
  • 元素是 React Apps 的最小构建块,一个元素描述屏幕中我们想看到的东西
  • 与浏览器 DOM 元素不同,React 元素是普通对象,创建的开销很小
  • React DOM 负责更新 DOM,将它与 React 元素保持一致
  • 不要将“元素”与“组件”混淆了,它们的关系是:组件是由元素组成的

1. 将元素渲染成 DOM

  1. <div id="root"></div>
  1. const element = <h1>Hello, world</h1>;
  2. ReactDOM.render(element, document.getElementById('root'));
  • 在HTML 选择一个 root DOM,它内部的所有内容都被 React DOM 管理
  • 要将 React 元素渲染成 root DOM,就把 React 元素和 root DOM 传递给 ReactDOM.render()

2. 更新渲染的元素

  1. function tick() {
  2. const element = (
  3. <div>
  4. <h1>Hello, world!</h1>
  5. <h2>It is {new Date().toLocaleTimeString()}.</h2>
  6. </div>
  7. );
  8. ReactDOM.render(element, document.getElementById('root'));
  9. }
  10. setInterval(tick, 1000);
  • React 元素是不可变的,一旦创建了一个元素,就不能改变它的子元素和属性
  • 我们可以创建一个新的元素,然后将其传递给 ReactDOM.render(),上面是一个滴答时钟的示例

3. React 只更新必要的部分

  • React DOM 将元素与其子元素与先前的做比较,然后只更新 DOM 能够达到状态的所必要的部分
  • 上个示例中,即使我们在每次“滴答”创建了一个新的元素来描述 UI 树,但只有文本节点的内容改变了, React DOM 才去更新它

四、组件 & Props

  • 组件可以使我们将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思
  • 从概念上看,组件类似于 JavaScript 函数
  • 它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素

1. 函数组件和类组件

  1. // 函数组件
  2. function Welcome(props) {
  3. return (<h1>Hello, {props.name}</h1>)
  4. }
  5. // 类组件
  6. class Welcome extends React.Component {
  7. render() {
  8. return <h1>Hello, {this.props.name}</h1>;
  9. }
  10. }
  • 定义一个组件最简单的方法是编写一个 JavaScript 函数
  • 示例中,它接受一个带有数据的单个“ props”(代表属性)对象参数,并返回一个 React 元素
  • 我们称这些组件为“函数组件”,因为它们实际上是 JavaScript 函数
  • 我们也可以使用 ES6 class 定义一个组件,从 React 角度看,他们是等价的

2. 渲染一个组件

  1. function Welcome(props) {
  2. return (<h1>Hello, {props.name}</h1>)
  3. }
  4. const element = (<Welcome name="Sara" />)
  5. ReactDOM.render(
  6. element,
  7. document.getElementById('root')
  8. )
  • React 元素不仅可以代表 DOM 标签,也可以表示为用户定义的组件
  • 当 React 看到一个元素表示为用户自定义组件时,它会把 JSX 的属性和子元素作为一个对象传递给它
    • 这个对象被称为“props”
  • 始终将组件以大写字母开头,React 会以小鞋字母开头的组件视为 DOM 标记
    • 比如
      表示一个 HTML div 标记
    • 表示一个组件,并且 Welcome 需要在作用域内使用

3. 组件之间组合

  1. function Welcome(props) {
  2. return (<h1>Hello, {props.name}</h1>)
  3. }
  4. function App() {
  5. return (
  6. <div>
  7. <Welcome name="Sara" />
  8. <Welcome name="Cahal" />
  9. <Welcome name="Edite" />
  10. </div>
  11. )
  12. }
  13. ReactDOM.render(
  14. <App />,
  15. document.getElementById('root')
  16. )
  • 组件可以在其输出中引用其他组件,这就可以让我们用同一组件做任意级别的抽象
    • 一个按钮,一个对话框,或是一个整个的屏幕,所有这些通常都以组件组件的形式表达
  • 通常,一个 React Apps 在顶层有一个 App 组件
  • 而如果是将 React 集成到现有的应用中,可能会从小组件,比如 Button组件开始自底向上工作到视图中

4. 提取组件

  1. // 初始的 Comment 组件
  2. function Comment(props) {
  3. return (
  4. <div className="Comment">
  5. <div className="UserInfo">
  6. <img className="Avatar"
  7. src={props.author.avatarUrl}
  8. alt={props.author.name}
  9. />
  10. <div className="UserInfo-name">
  11. {props.author.name}
  12. </div>
  13. </div>
  14. <div className="Comment-text">
  15. {props.text}
  16. </div>
  17. <div className="Comment-date">
  18. {formatDate(props.date)}
  19. </div>
  20. </div>
  21. );
  22. }
  23. // 提取 Avatar 组件
  24. function Avatar(props) {
  25. return (
  26. <img className="Avatar"
  27. src={props.user.avatarUrl}
  28. alt={props.user.name}
  29. />
  30. );
  31. }
  32. // 提取 UserInfo 组件
  33. function UserInfo(props) {
  34. return (
  35. <div className="UserInfo">
  36. <Avatar user={props.user} />
  37. <div className="UserInfo-name">
  38. {props.user.name}
  39. </div>
  40. </div>
  41. );
  42. }
  43. // 拆分后的 Comment 组件
  44. function Comment(props) {
  45. return (
  46. <div className="Comment">
  47. <UserInfo user={props.author} />
  48. <div className="Comment-text">
  49. {props.text}
  50. </div>
  51. <div className="Comment-date">
  52. {formatDate(props.date)}
  53. </div>
  54. </div>
  55. );
  56. }
  • 不要害怕将组件拆分成更小的组件
  • 建议从组件自身的角度命名组件,而不是根据它被使用的上下文
  • 提取组件一开始看起来是个繁重的工作,但在大型应用中,构建可复用组件库是值得的

5. Props 是只读的

  1. // 这是一个纯函数,不修改输入
  2. function sum(a, b) {
  3. return a + b;
  4. }
  5. // 这个函数不纯,它改变了自己的输入
  6. function withdraw(account, amount) {
  7. account.total -= amount;
  8. }
  • 当声明了一个函数组件或类组件时,它一定不能修改它的 props
  • 纯函数不会尝试改变它的输入,并会为相同的输入返回相同的结果
  • 所有 React 组件必须像纯函数一样使用它们的 props
  • 当然,UIs 是动态的,并会随着时间推移而变化,State 允许 React 组件随时间更改其输出,以相应用户操作、网络相应和其他任何东西,而不违反此规则

五、State & 生命周期

「更新渲染的元素」小结中我们调用 ReactDom.render() 来改变渲染出的结果,当我们有了 State,我们就可以将滴答时钟封装成可复用的组件,它将设置它自己的定时器,并在每秒更新一次

1. 将函数组件转换为类组件

  1. class Clock extends React.Component {
  2. render() {
  3. return (
  4. <div>
  5. <h1>Hello, world!</h1>
  6. <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
  7. </div>
  8. );
  9. }
  10. }
  • 把函数组件转换成类组件通常有 5 个步骤:
    1. 创建 ES6 class 类并继承 React.Component
    2. 添加一个名叫 render() 的空方法
    3. 将函数体移到 render() 方法里
    4. render() 里的 props 替换成 this.props
    5. 删除剩余的空函数声明
  • render() 方法会在每次更新的时候被调用,一旦我们渲染 <Clock /> 到相同的 DOM 节点时,只有一个 Clock 类的实例会被使用,这让我们可以使用额外的特性,比如局部 state 和生命周期方法

2. 给 class 添加局部 State

  1. class Clock extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {date: new Date()};
  5. }
  6. render() {
  7. return (
  8. <div>
  9. <h1>Hello, world!</h1>
  10. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  11. </div>
  12. );
  13. }
  14. }
  15. ReactDOM.render(
  16. <Clock />,
  17. document.getElementById('root')
  18. );
  • 添加一个初始赋值 this.state 的类构造函数
  • 类组件应该始终使用 props 来调用构造函数

3. 给 class 添加生命周期方法

  1. class Clock extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {date: new Date()};
  5. }
  6. componentDidMount() {
  7. this.timerID = setInterval(
  8. () => this.tick(),
  9. 1000
  10. );
  11. }
  12. componentWillUnmount() {
  13. clearInterval(this.timerID);
  14. }
  15. tick() {
  16. this.setState({
  17. date: new Date()
  18. });
  19. }
  20. render() {
  21. return (
  22. <div>
  23. <h1>Hello, world!</h1>
  24. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  25. </div>
  26. );
  27. }
  28. }
  29. ReactDOM.render(
  30. <Clock />,
  31. document.getElementById('root')
  32. );
  • <Clock> 第一次被渲染成 DOM 时,我们设置一个定时器。React 中将这个阶段被称为“挂载”
  • <Clock> 被移除时,我们将定时器清除。React 中将这个阶段被称为“卸载”
  • 我们可以在类组件上声明特殊的方法,以便在组件在挂载和卸载时运行一些代码
    • 这些特殊的方法被称为“生命周期”
  • componentDidMount() 方法在组件输出被渲染完毕后执行,是设置定时器的好时机
  • 除了 this.props 和 this.state,我们可以自由地向勒中添加其他字段,比如 this.timeId
  • 我们在 componentWillUnmount() 生命周期中清除定时器
  • 最后我们实现一个 tick() 方法,让 Clock 每秒运行一次,它使用 this.setState() 来安排对局部 state 的更新

4. 正确地使用 State

  1. // 1. 不要直接修改状态。相反,我们要使用 setState()
  2. // Wrong
  3. this.state.comment = 'Hello';
  4. // Correct
  5. this.setState({comment: 'Hello'});
  6. // 2. state 更新可能是异步的
  7. // Wrong
  8. this.setState({
  9. counter: this.state.counter + this.props.increment,
  10. });
  11. // Correct
  12. this.setState((state, props) => ({
  13. counter: state.counter + props.increment
  14. }));
  15. // 3. state 更新会合并
  16. constructor(props) {
  17. super(props);
  18. this.state = {
  19. posts: [],
  20. comments: []
  21. };
  22. }
  23. componentDidMount() {
  24. fetchPosts().then(response => {
  25. this.setState({
  26. posts: response.posts
  27. });
  28. });
  29. fetchComments().then(response => {
  30. this.setState({
  31. comments: response.comments
  32. });
  33. });
  34. }

关于 setState(),我们应该要知道三件事

  1. 不要直接修改状态。相反,我们要使用 setState()
    • 直接修改状态不会重新渲染组件
  2. state 更新可能是异步的
    • React 为了提高性能会将一批 setState() 调用合并成一个
    • 因此 this.statethis.props 可能会异步更新,我们不能依赖的它们的值来计算下一个状态
    • 如果想要正确依赖 this.statethis.props,可以使用 setState() 的第二个调用方式,它接受一个函数,第一个参数是 state,第二个参数是 props
  3. state 更新会合并
    • 在调用 setState() 的时,React 将提供的对象合并到当前的 state 中,而这个合并是浅合并

5. 数据流向下流动

  • 父组件和子组件不知道一个组件是有状态的还是无状态的,也不关心它是函数组件还是类组件
  • 这就是为什么 state 被称为局部和封装的原因。除了它自己,任何其他组件无法访问它
  • 一个组件可以选择是否将 state 作为 props 传递给它的子组件
  • 这通常称为“自顶向下”或“单向数据流”,任何 state 总是由某个特定组件拥有,并且任何数据和 UI 只能影响 tree 中“低于”它们的组件
  • 在 React Apps 中,组件是否有状态被认为是组件的实现细节,这些细节可能会随着时间推移而改变
    • 我们可以在有状态组件内部使用无状态组件
    • 也可以在无状态组件内部使用有状态组件

六、事件处理

1. 与 DOM 事件处理做对比

  1. <button onclick="activateLasers()">
  2. Activate Lasers
  3. </button>
  4. <form onsubmit="console.log('You clicked submit.'); return false">
  5. <button type="submit">Submit</button>
  6. </form>
  1. <button onClick={activateLasers}>
  2. Activate Lasers
  3. </button>
  4. function Form() {
  5. function handleSubmit(e) {
  6. e.preventDefault();
  7. console.log('You clicked submit.');
  8. }
  9. return (
  10. <form onSubmit={handleSubmit}>
  11. <button type="submit">Submit</button>
  12. </form>
  13. );
  14. }

React 元素中的事件处理和在 DOM 处理事件很类似,但有些许不同

  • React 的事件名使用小驼峰式,而不是小写命名
  • 在 JSX 中将函数作为事件处理,而不是字符串
  • 在 React 不能返回 false 来组织默认事件,必须显性地调用 preventDefault
    • 这里面的 e 是一个合成事件,React 根据 W3C 规范定义了这些合成事件

2. React 绑定事件的方法

  1. class Toggle extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {isToggleOn: true};
  5. // This binding is necessary to make `this` work in the callback
  6. this.handleClick = this.handleClick.bind(this);
  7. }
  8. handleClick() {
  9. this.setState(prevState => ({
  10. isToggleOn: !prevState.isToggleOn
  11. }));
  12. }
  13. render() {
  14. return (
  15. <button onClick={this.handleClick}>
  16. {this.state.isToggleOn ? 'ON' : 'OFF'}
  17. </button>
  18. );
  19. }
  20. }
  21. ReactDOM.render(
  22. <Toggle />,
  23. document.getElementById('root')
  24. );
  25. // 使用公共类字段语法
  26. class LoggingButton extends React.Component {
  27. // This syntax ensures `this` is bound within handleClick.
  28. // Warning: this is *experimental* syntax.
  29. handleClick = () => {
  30. console.log('this is:', this);
  31. }
  32. render() {
  33. return (
  34. <button onClick={this.handleClick}>
  35. Click me
  36. </button>
  37. );
  38. }
  39. }
  40. // c. 事件函数使用箭头函数
  41. class LoggingButton extends React.Component {
  42. handleClick() {
  43. console.log('this is:', this);
  44. }
  45. render() {
  46. // This syntax ensures `this` is bound within handleClick
  47. return (
  48. <button onClick={() => this.handleClick()}>
  49. Click me
  50. </button>
  51. );
  52. }
  53. }
  • 在使用 React 时,通常不需要调用 addEventListener 在 DOM 元素创建后将监听器添加到该元素,相反,只需要在最初渲染元素时提供一个监听器
  • 当使用 ES6类 定义组件时,常见的模式是将事件处理程序作为类上的方法
  • 使用时我们要时刻注意 this 问题,handleClick 中的 this 需要保证指向 Toggle 实例,有三种方法保存
    1. 在构造函数里使用绑定 this
    2. 使用公共类字段语法
    3. 回调函数使用箭头函数包裹
  • 第三种方法的问题在于每次渲染 LoggingButton 时会创建一个不同的回调,在大多数情况下,它很好,但是如果这个回调作为一个 props 传递给下面组件,那么这些组件可能会执行额外的渲染
  • 所以通常建议在构造函数中绑定或使用类字段语法,以避免此类性能问题

3. 将参数传递给事件处理器

  1. <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
  2. <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
  • 上面两行是等价的,分别使用箭头函数和 Function.prototype.bind
  • 在这两种情况下,表示 React 事件的 e 将作为 ID 之后的第二个参数传递

七、条件渲染

1. 和 JavaScript 一样条件渲染

  1. function UserGreeting(props) {
  2. return <h1>Welcome back!</h1>;
  3. }
  4. function GuestGreeting(props) {
  5. return <h1>Please sign up.</h1>;
  6. }
  7. function Greeting(props) {
  8. const isLoggedIn = props.isLoggedIn;
  9. if (isLoggedIn) {
  10. return <UserGreeting />;
  11. }
  12. return <GuestGreeting />;
  13. }
  14. ReactDOM.render(
  15. // Try changing to isLoggedIn={true}:
  16. <Greeting isLoggedIn={false} />,
  17. document.getElementById('root')
  18. );
  • React 中的条件渲染与 JavaScript 中的条件渲染工作方式相同,使用 if 或其他条件运算符就行了

2. 元素变量

  1. class LoginControl extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.handleLoginClick = this.handleLoginClick.bind(this);
  5. this.handleLogoutClick = this.handleLogoutClick.bind(this);
  6. this.state = {isLoggedIn: false};
  7. }
  8. handleLoginClick() {
  9. this.setState({isLoggedIn: true});
  10. }
  11. handleLogoutClick() {
  12. this.setState({isLoggedIn: false});
  13. }
  14. render() {
  15. const isLoggedIn = this.state.isLoggedIn;
  16. let button;
  17. if (isLoggedIn) {
  18. button = <LogoutButton onClick={this.handleLogoutClick} />;
  19. } else {
  20. button = <LoginButton onClick={this.handleLoginClick} />;
  21. }
  22. return (
  23. <div>
  24. <Greeting isLoggedIn={isLoggedIn} />
  25. {button}
  26. </div>
  27. );
  28. }
  29. }
  30. ReactDOM.render(
  31. <LoginControl />,
  32. document.getElementById('root')
  33. );
  • 使用变量存储元素,可以让我们有条件地渲染组件的一部分

3. 操作符 &&(内联的 if)

  1. function Mailbox(props) {
  2. const unreadMessages = props.unreadMessages;
  3. return (
  4. <div>
  5. <h1>Hello!</h1>
  6. {unreadMessages.length > 0 &&
  7. <h2>
  8. You have {unreadMessages.length} unread messages.
  9. </h2>
  10. }
  11. </div>
  12. );
  13. }
  14. const messages = ['React', 'Re: React', 'Re:Re: React'];
  15. ReactDOM.render(
  16. <Mailbox unreadMessages={messages} />,
  17. document.getElementById('root')
  18. );
  19. // 返回的 <div>0<div>
  20. render() {
  21. const count = 0;
  22. return (
  23. <div>
  24. { count && <h1>Messages: {count}</h1>}
  25. </div>
  26. );
  27. }
  • 将表达式包装在大括号中来在 JSX 中嵌入表达式,里面使用操作符 &&
  • true && expression 总是计算为 expression,而 false & expression 总是计算为 false
  • 如果条件为 true,&& 右边的元素就会是输出结果,如果条件是 false,React 就会直接跳过
  • 注意,如果返回的 falsy 表达式,React 会渲染出 falsy 表达式!

4. ?:表达式(内联的 if-else)

  1. render() {
  2. const isLoggedIn = this.state.isLoggedIn;
  3. return (
  4. <div>
  5. The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
  6. </div>
  7. );
  8. }
  9. render() {
  10. const isLoggedIn = this.state.isLoggedIn;
  11. return (
  12. <div>
  13. {isLoggedIn
  14. ? <LogoutButton onClick={this.handleLogoutClick} />
  15. : <LoginButton onClick={this.handleLoginClick} />
  16. }
  17. </div>
  18. );
  19. }
  • 使用 JavaScript 条件操作符 condition ? true: false
  • 使用哪种条件渲染的方式取决于我们认为哪个更有可读性
  • 当条件变得过于复杂时,它可能是提取成组件的好时机

5. 阻止组件渲染

  1. function WarningBanner(props) {
  2. if (!props.warn) {
  3. return null;
  4. }
  5. return (
  6. <div className="warning">
  7. Warning!
  8. </div>
  9. );
  10. }
  11. class Page extends React.Component {
  12. constructor(props) {
  13. super(props);
  14. this.state = {showWarning: true};
  15. this.handleToggleClick = this.handleToggleClick.bind(this);
  16. }
  17. handleToggleClick() {
  18. this.setState(state => ({
  19. showWarning: !state.showWarning
  20. }));
  21. }
  22. render() {
  23. return (
  24. <div>
  25. <WarningBanner warn={this.state.showWarning} />
  26. <button onClick={this.handleToggleClick}>
  27. {this.state.showWarning ? 'Hide' : 'Show'}
  28. </button>
  29. </div>
  30. );
  31. }
  32. }
  33. ReactDOM.render(
  34. <Page />,
  35. document.getElementById('root')
  36. );
  • 在极少数的情况下,如果希望每个组件隐藏自己,而它由另一个组件渲染,可以 return null 达到目的

八、列表 && keys

1. 渲染多个组件

  1. const numbers = [1, 2, 3, 4, 5];
  2. const listItems = numbers.map((number) =>
  3. (<li>{number}</li>)
  4. );
  5. ReactDOM.render(
  6. <ul>{listItems}</ul>,
  7. document.getElementById('root')
  8. );
  • 构建一个元素集合,并使用大括号 {} 将它们放入 JSX 中

2. 基本列表组件

  1. function NumberList(props) {
  2. const numbers = props.numbers;
  3. const listItems = numbers.map((number) =>
  4. <li key={number.toString()}>
  5. {number}
  6. </li>
  7. );
  8. return (
  9. <ul>{listItems}</ul>
  10. );
  11. }
  12. const numbers = [1, 2, 3, 4, 5];
  13. ReactDOM.render(
  14. <NumberList numbers={numbers} />,
  15. document.getElementById('root')
  16. );
  • 通常我们会在一个组件中渲染列表
  • 我们要为每个列表项分配一个 key

3. 使用 keys

  1. const todoItems = todos.map((todo) =>
  2. (<li key={todo.id}>
  3. {todo.text}
  4. </li>)
  5. );
  6. const todoItems2 = todos.map((todo, index) =>
  7. // Only do this if items have no stable IDs
  8. <li key={index}>
  9. {todo.text}
  10. </li>
  11. );
  • keys 帮助 React 识别哪些项目被更改了、添加了、或被删除了
  • 需要给列表中的每个元素 key,让元素具有稳定的标识
  • 选择 key 的最好的方法就是使用一个在同级列表项中唯一标识它的字符串,大多数情况下是使用数据中 id
  • 如果没有稳定的 id 给列表项,可以使用列表项索引作为最后的手段
  • 如果项目的顺序可能发生变化,那么不建议用对键使用索引,这会对性能产生不好的影响

4. 用 keys 提取组件

  1. function ListItem(props) {
  2. // Correct! There is no need to specify the key here:
  3. return <li>{props.value}</li>;
  4. }
  5. function NumberList(props) {
  6. const numbers = props.numbers;
  7. const listItems = numbers.map((number) =>
  8. // Correct! Key should be specified inside the array.
  9. <ListItem key={number.toString()} value={number} />
  10. );
  11. return (
  12. <ul>
  13. {listItems}
  14. </ul>
  15. );
  16. }
  17. const numbers = [1, 2, 3, 4, 5];
  18. ReactDOM.render(
  19. <NumberList numbers={numbers} />,
  20. document.getElementById('root')
  21. );
  • keys 只有在周围数组的上下文才有意义
  • 一个很好的经验法则是 map() 中的元素需要使用 keys

5. key 在兄弟节点之间必须唯一

  1. function Blog(props) {
  2. const sidebar = (
  3. <ul>
  4. {props.posts.map((post) =>
  5. <li key={post.id}>
  6. {post.title}
  7. </li>
  8. )}
  9. </ul>
  10. );
  11. const content = props.posts.map((post) =>
  12. <div key={post.id}>
  13. <h3>{post.title}</h3>
  14. <p>{post.content}</p>
  15. </div>
  16. );
  17. return (
  18. <div>
  19. {sidebar}
  20. <hr />
  21. {content}
  22. </div>
  23. );
  24. }
  25. const posts = [
  26. {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  27. {id: 2, title: 'Installation', content: 'You can install React from npm.'}
  28. ];
  29. ReactDOM.render(
  30. <Blog posts={posts} />,
  31. document.getElementById('root')
  32. );
  • 数组中的 key 在其兄弟数组中是唯一的,而不必是全局唯一,不同的数组可以使用相同的键
  • key 用于 React 的提示,但不会传递给我们写的组件,如果想用它的值,需要显性地换个名字作为 props

6. JSX 中嵌入 map()

  1. function NumberList(props) {
  2. const numbers = props.numbers;
  3. return (
  4. <ul>
  5. {numbers.map((number) =>
  6. <ListItem key={number.toString()}
  7. value={number} />
  8. )}
  9. </ul>
  10. );
  11. }
  • JSX 允许在花括号中嵌入任何表达式,所以我们可以内联 map() 的结果
  • 有时候这会导致更清晰的代码,但是这种风格会被滥用,如果 map() 嵌套得太多,可能是提取组件的好时机

1. 使用列表元素可以获得多倍快乐

React 元素的列表能够让我们获得多倍快乐,就像下面这样

  1. const numbers = [1, 2, 3, 4, 5];
  2. const listItems = numbers.map((number) =>
  3. <li>{number}</li>
  4. );
  5. ReactDOM.render(
  6. <ul>{listItems}</ul>,
  7. document.getElementById('root')
  8. )

2. 列表元素必须要有独一无二的 key

当运行以上代码会出现一个告警:Warning: Each child in an array or iterator should have a unique “key” prop. 意思是当创建一个元素时,必须包含一个独一无二的 key。

九、Forms

1. 原生的 form

  1. <form>
  2. <label>
  3. Name:
  4. <input type="text" name="name" />
  5. </label>
  6. <input type="submit" value="Submit" />
  7. </form>
  • 在React 中,form 元素与其他 DOM 元素不同,因为 form 元素通常会保存一些内部状态,例如,原生 HTML 的 form 只接受一个名字
  • 当用户提交表单,此表单由默认的行为

2. 受控组件,文字输入 input 标签

  1. class NameForm extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {value: ''};
  5. this.handleChange = this.handleChange.bind(this);
  6. this.handleSubmit = this.handleSubmit.bind(this);
  7. }
  8. handleChange(event) {
  9. this.setState({value: event.target.value});
  10. }
  11. handleSubmit(event) {
  12. alert('A name was submitted: ' + this.state.value);
  13. event.preventDefault();
  14. }
  15. render() {
  16. return (
  17. <form onSubmit={this.handleSubmit}>
  18. <label>
  19. Name:
  20. <input type="text" value={this.state.value} onChange={this.handleChange} />
  21. </label>
  22. <input type="submit" value="Submit" />
  23. </form>
  24. );
  25. }
  26. }
  • 在 HTML 中,像