【React】如何实现子组件修改父组件的state

1,819 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

本文主要内容:通过编写可独立点击变色的多个盒子模型,理解在子组件中修改父组件state的方法

项目预览与分析

image.png

定义了六个盒子,要实现点击盒子就能够变色的功能

静态页面的六个盒子编写

基础框架

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文件中读取每个盒子的数据,每条数据为一个对象,有 idon 两个属性,示例如下:

{
    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>中添加颜色会让所有盒子都是同一个颜色

image.png

创建盒子组件

所以为了给每个盒子单独上色,需要将它们分成一个个独立的组件

新建 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 stateBox 组件需要接收 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}/>

效果预览

image.png

实现动态分别控制六个盒子

方法一:定义组件内的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>
    )
}

效果预览如下

71mhh-i3umb.gif

方法二:子组件和父组件使用一致的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>

可以得到如下效果

u289o-dphk3.gif

然而这却会招致另一个问题,无论点击哪个盒子,我们都只是执行同样的函数,那么我们就无法对应地修改 App state 的值。

即 如果我们想在之后使用 setState,我们首先得知道是哪一个盒子触发了 setState,这样才能定向地修改成需要的 state 的值

所以接下来的目标就是让 toggle函数能够知道我们点击了哪一个盒子,可以选择在这个函数中传递一个参数来标识对应的身份,比如这里可以这样写

function toggle(id) {
  console.log(id)
}

toggle()函数中传入了一个每个盒子独有的id

所以接下来我们需要去组件 Boxtoggle传入指定的值,我们这里可以这样写

<div 
    style={styles} 
    className="box" 
    onClick={()=>props.toggle(props.id)}
>
</div>

onClick 的事件函数不再是props传入的函数,而是以props.toggle(props.id)为返回值的组件本地函数,这样就能将App传入的盒子的id重新传回给 App 中的 toggle() 函数,看一下运行效果

26fd1-bdrjz.gif

利用接收到的身份信息修改对应的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的盒子,其由黑色变为白色为例。

  1. 首先是我们触发了点击这个事件,进入 onClick 的函数
  2. onClick 的函数 ()=>props.toggle(props.id) 需要进入父组件传入的那个函数中运行
  3. 回到父组件的 toggle() 函数,接收的参数id正是传入的props.id
  4. 所以根据这个参数id,能够写出选择语句更新state数组
  5. 最终再次将这个数组渲染到页面上,实现效果