前情提要:在上一篇网页UI组件化 — React component,大致了解 React 最重要的核心 — Component,并且学会了在组件内控制资料的处理,接下来就来探讨 React 其他更便利的功能。
在某些状况下,你可能会希望依据 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>
);
}
目前我们是直接复制 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>
);
在 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,能够更好的管理应用程序中的资料状态。
如果文章中有错误的地方,要麻烦各位大大不吝赐教;喜欢的话,也要记得帮我按赞订阅喔❤️
参考资料
前几天我们已经启用 VPC Flow Log、CloudFront Log,接下来我们就是要来实作 ...
如果有错误,欢迎留言指教~ Q_Q 还没写完辣 除了用 React 帮你定义的 Hook 你也可以...
其实这篇是想给自己一个警惕, 做人不要太贪心, 何谓太贪心? 我目前大三上, 这学期修了30学分, ...
我们需要使用 FRRouting,若还没安装的话,请先安装一下 这次使用的系统为 Ubuntu 20...
本篇来介绍 v-on 指令的特别之处,使用 JavaScript 撰写一个事件处理,除了 DOM 的...