本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
本文主要内容:通过编写可独立点击变色的多个盒子模型,理解在子组件中修改父组件state的方法
项目预览与分析
定义了六个盒子,要实现点击盒子就能够变色的功能
静态页面的六个盒子编写
基础框架
import React from "react"
import boxes from "./boxes"
export default function App() {
const [squares, setSquares] = React.useState(boxes)
const squareElements = squares.map(square => (
<div className="box" key={square.id}></div>
))
return (
<main>
{squareElements}
</main>
)
}
从boxes.js文件中读取每个盒子的数据,每条数据为一个对象,有 id 和 on 两个属性,示例如下:
{
id: 1,
on: true
}
所以这样我们就完成了未上色的盒子的编写,接下来根据 on 属性值,利用三元表达式实现上色
给盒子上色
在静态的网页编写中,我们一般在CSS文件里写页面的样式,然而想要动态地修改样式,这里需要用到一个给<div> 中的 style 赋值的对象。所以编写 styles 对象如下:
const styles = {
backgroundColor: "black"
}
修改<div>如下
<div style={styles} className="box" key={square.id}></div>
此时所有盒子都被上了黑色,显然直接在App.js的<div>中添加颜色会让所有盒子都是同一个颜色
创建盒子组件
所以为了给每个盒子单独上色,需要将它们分成一个个独立的组件
新建 Box.js 文件,将 App.js中的 <div> 移入Box.js,并在App.js中将 <div>改为 <Box /> 组件,代码如下
import React from "react"
export default function Box() {
return (
<div className="box"></div>
)
}
这时每个盒子就被单独分成了一个组件
利用props传递state
终于进入一部分正题,因为Box 组件无法直接对父组件的state进行操作,所以为了获取在 App.js 中定义的squares state,Box 组件需要接收 App 组件传递的 props
然后再利用三元表达式,代码如下
import React from "react"
export default function Box(props) {
const styles = {
backgroundColor: props.on ? "#222222" : "#ffffff"
}
return (
<div style={styles} className="box"></div>
)
}
接收了 props ,并且进行了判断选择和渲染
至于 App 组件中的传递代码如下
<Box key={square.key} on={square.on}/>
效果预览
实现动态分别控制六个盒子
方法一:定义组件内的state(不推荐)
这个方法很容易想到也很容易懂,就是在 Box 组件里,再定义一遍 on state ,用传入的props的值当初始值,那么再进行取反的setState就能很容易地实现功能,代码如下
import React from "react"
export default function Box(props) {
const [on,setOn] = React.useState(props.on)
const styles = {
backgroundColor: on ? "#222222" : "#ffffff"
}
function toggleOn(){
setOn(prevOn => !prevOn)
}
return (
<div style={styles} className="box" onClick={toggleOn}></div>
)
}
效果预览如下
方法二:子组件和父组件使用一致的state
上面的方法显然不够好,定义了多个类似的state容易混淆,在资源的利用上也相当不优雅
所以我们接下来想要写一个能让子组件对父组件的state进行修改,然后父组件再将state回传给子组件从而进行渲染的方法。
写一个位于APP但是BOX可以执行的函数
要对组件内部的 state 进行操作,那就需要在对应的组件内部定义这个操作函数
所以作为开始,我们先写一个最简单的可执行函数,如下写了一个 toggle() 函数,并且通过props传入了 Box 组件中
function toggle() {
console.log("Clicked!")
}
const squareElements = squares.map(square => (
<Box
key={square.id}
on={square.on}
toggle={toggle}
/>
))
然后同样在 Box 组件中接收这个函数,
<div style={styles} className="box" onClick={props.toggle}></div>
可以得到如下效果
然而这却会招致另一个问题,无论点击哪个盒子,我们都只是执行同样的函数,那么我们就无法对应地修改 App state 的值。
即 如果我们想在之后使用 setState,我们首先得知道是哪一个盒子触发了 setState,这样才能定向地修改成需要的 state 的值
所以接下来的目标就是让 toggle函数能够知道我们点击了哪一个盒子,可以选择在这个函数中传递一个参数来标识对应的身份,比如这里可以这样写
function toggle(id) {
console.log(id)
}
向toggle()函数中传入了一个每个盒子独有的id
所以接下来我们需要去组件 Box 向toggle传入指定的值,我们这里可以这样写
<div
style={styles}
className="box"
onClick={()=>props.toggle(props.id)}
>
</div>
onClick 的事件函数不再是props传入的函数,而是以props.toggle(props.id)为返回值的组件本地函数,这样就能将App传入的盒子的id重新传回给 App 中的 toggle() 函数,看一下运行效果
利用接收到的身份信息修改对应的state
在上面一段里,我们已经能够点击盒子对应地返回这个盒子的 id 了
那么接下来就需要根据得到的这个 id 来修改 state 中对应的 on 的值
可以得到如下代码
function toggle(id) {
setSquares(prevSquares =>{
return prevSquares.map((square) => {
return square.id === id ? {...square, on:!square.on} : square
})
})
}
在这个函数里我们要使用setState来更新state,至于这个更新的值需要考虑先前的值,使用map()来对先前的squares数组进行调整: 如果是选中的那个盒子,那么将 on 取反,如果不是,则按照原样
所以我们就能看到最终的效果与方法一一致,但是在写法上要优雅不少。
小结
本文的主要内容就是实现六个盒子通过修改统一的父组件state,实现独立控制自己的颜色,仅仅看了上面的编写过程还是有一些迷茫
所以接下来将对这个程序的运行流程进行一番总结
以点击id为1的盒子,其由黑色变为白色为例。
- 首先是我们触发了点击这个事件,进入
onClick的函数 onClick的函数()=>props.toggle(props.id)需要进入父组件传入的那个函数中运行- 回到父组件的
toggle()函数,接收的参数id正是传入的props.id - 所以根据这个参数
id,能够写出选择语句更新state数组 - 最终再次将这个数组渲染到页面上,实现效果