[Day 21 - React] 今晚我想来点,React的其他功能

前情提要:在上一篇网页UI组件化 — React component,大致了解 React 最重要的核心 — Component,并且学会了在组件内控制资料的处理,接下来就来探讨 React 其他更便利的功能。

有条件的 Render Element

在某些状况下,你可能会希望依据 State 资料状态,来改变 Render 的内容,React 就允许使用 JavaScript 的 if 或三元运算子,控制改变要渲染的 Element。 比如我们延续上一篇文章使用的计数器范例,要在分数小於零时控制 minus_btn 不显示,不让使用者减少分数。

在 Counter.js 宣告一个变数 minus_btn 储存 Element,初始值为 null,代表没有指定 Element,显示在画面上就会是空,会是分数小於零不显示 minus_btn 的情况。

Counter.js

export default function Counter() {
  ...
  let minus_btn = null;

  return (
    <div className="Counter">
      ...
    </div>
  );
}

接着要利用 if,在分数大於零的时候要指定 Element 给变数 minus_btn,并将原本 render() 内的 Element 换成 minus_btn 控制。

export default function Counter() {
  ...
  let minus_btn = null;

  if (score > 0)
    minus_btn = <button onClick={() => setScore(score - 1)}>minus</button>;

  return (
    <div className="Counter">
      ...
      <button onClick={() => setScore(score + 1)}>Plus</button>
      {minus_btn}
    </div>
  );
}

这样就完成条件 Render 了

另外你还可以换成用三元运算子条件 ? (条件为 true 时的值) : (条件为 false 时的值),直接在 render() 内改变 Element 的显示。

export default function Counter() {
  ...

  return (
    <div className="Counter">
      ...
      <button onClick={() => setScore(score + 1)}>Plus</button>
      {score > 0 ? <button onClick={() => setScore(score - 1)}>minus</button> : null}
    </div>
  );
}

Render Lists

目前我们是直接复制 Member Component 产生三个成员,假设成员数目增加,这种复制的做法就显得冗长又没效率。所以可以使用 JavaScript 的阵列处理方法 map(),该方法会合并阵列每一个元素回传的运算结果,建立一个新的阵列。

遍历资料 members 内部的每个成员 member,生成 Member Component 并代入名字资料,再将所有回传的结果组合新阵列指定给变数 element_list,就可以直接依据资料自动 Render 多个 Component。

App.js

export default function App() {
  const members = ["May", "Julia", "Bob"];
  const element_list = members.map((member) => <Member name={member} />);

  return (
    <div className="App">
      <h1>Member Score</h1>
      {element_list}
    </div>
  );
}

但这时候你会看到 Console 视窗出现一个错误:Warning: Each child in list should have a unique "key" prop.,告诉我们应该为每个 <Member /> 新增一个不重复的 key

为什麽需要Key?Key 的功用在於可以透过帮助 React 来分辨哪些项目被改变、增加或删除,进而进行画面的更新,为了不让 React 搞错每个 Element 的身分,每个元素都要有一个独立的 Key 值。所以我们就直接为 <Member /> 新增 key,指定每位成员的姓名为其值。

export default function App() {
  const members = ["May", "Julia", "Bob"];
  const element_list = members.map((member) => <Member key={member} name={member} />);

  return (
    <div className="App">
      <h1>Member Score</h1>
      {element_list}
    </div>
  );
}

之前我们有提到在 JSX 内可以放入任何表达式,前面的作法是另外宣告变数 element_list 存放列表元素,再用大括号包起来放到 JSX 中;另一个作法则是可以改成将 map() 放在 JSX 中:

return (
    <div className="App">
      ...
      {members.map((member) => (
        <Member key={member} name={member} />
      ))}
    </div>
);

提升State层级

在 React 中,资料传递的方式属於单向资料流,代表所有的资料都只能透过 props,从父层 Component 往子层 Component 传递。但当我们希望不同的 Component 间可以共享某些数据,比如接下来想要在 Member Component 里,显示成员成绩的状态是合格还是不及格,就需要共享 Counter Component 内的 Score State。

解决的方法就是提升 State 的层级,将共享的资料提升到组件汇流的最上层,举例来说如下图,B 需要共享 C 的 Data State,那我们就将 State 提升到父层组件 A,再透过 Props 传递 Data 给 B 和 C。

在我们的范例中,设定分数大於零就显示 PASS、否则显示 FAIL,Member Component 就需要共享 Counter Component 内的 Score State。所以将 State 提升到最上层的组件 Member Component,并将资料透过 Props 传递给 Counter Compenent。

Member.js

import { useState } from "react";
import Counter from "./Counter";

export default function Member(props) {
  const [score, setScore] = useState(0);

  return (
    <div className="member">
      <h2>{props.name}</h2>
      <div>{score > 0 ? "PASS" : "FAIL"}</div>
      <Counter score={score}/>
    </div>
  );
}

问题是 Props 是唯读的,不能在子组件内修改父层 State 的值,那要如何修改计数器按钮 onClick 所触发的动作?我们可以透过 Props 传递动作,先在 Member Component 设定好按下按钮後,修改 Score State 的动作handleScoreChange,再将函式作为 Props 传递给 Counter Component 呼叫。

Member.js

export default function Member(props) {
  ...
  
  function handleScoreChange(number) {
    //接收分数加减完成後的number
    setScore(number);
  }

  return (
    <div className="member">
      ...
      <Counter score={score} handleScoreChange={handleScoreChange}/>
    </div>
  );
}

Counter.js

export default function Counter(props) {
  return (
    <div className="Counter">
      ...
      <button onClick={() => props.handleScoreChange(props.score + 1)}>
        Plus
      </button>
      {props.score > 0 && (
        <button onClick={() => props.handleScoreChange(props.score - 1)}>
          minus
        </button>
      )}
    </div>
  );
}

分数大於零就显示PASS、否则显示FAIL


小结

学会了 React 其他的功能,包括如何有条件的 Render Element、用 map() 自动生成列表和提升 State 层级,React 本身的介绍差不多就告一个段落了。当你从单纯撰写 HTML、CSS、JS,慢慢转变为在专案中套入框架,用框架的模式去思考,就会体会到这种模组化的方式可以帮助我们,让专案更好开发与管理。接下来就会进一步提到如何使用 Redux,能够更好的管理应用程序中的资料状态。

范例程序码

如果文章中有错误的地方,要麻烦各位大大不吝赐教;喜欢的话,也要记得帮我按赞订阅喔❤️

参考资料


<<:  Day 20 BeautifulSoup模组二

>>:  Day-22 常用System Call

【Day 23】 AWS Kinesis - Data Streams vs Data Firehose 两者差异

前几天我们已经启用 VPC Flow Log、CloudFront Log,接下来我们就是要来实作 ...

Day 18 - custom hook

如果有错误,欢迎留言指教~ Q_Q 还没写完辣 除了用 React 帮你定义的 Hook 你也可以...

[Day_30]不要贪心

其实这篇是想给自己一个警惕, 做人不要太贪心, 何谓太贪心? 我目前大三上, 这学期修了30学分, ...

Day 17 - Linux 上设定 PBR

我们需要使用 FRRouting,若还没安装的话,请先安装一下 这次使用的系统为 Ubuntu 20...

Vue.js 从零开始:v-on

本篇来介绍 v-on 指令的特别之处,使用 JavaScript 撰写一个事件处理,除了 DOM 的...