React 父子组件通信

717 阅读3分钟

前言

最近开发项目,碰到了一个父子组件传输数据的需求,也碰上了两个有点‘棘手’的问题,这里记录一下我的解决思路。先展示一下基本的代码和页面效果:

父组件:

import TableList from 'src/components/TableList'
import { Button } from 'antd'

const List: React.FC = () => {

  return (
    <div style={{marginTop: '20px'}}>
      <TableList />
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          height: '40px',
          backgroundColor:'white'
        }}
      >
        <div>
          <Button type="text">X selected</Button>
          <Button type="link">Select All</Button>
          <Button type="link">Select Invert</Button>
          <Button type="link">Deselect All</Button>
        </div>
        <div>
          <Button type="primary">Export</Button>
        </div>
      </div>
    </div>
  )
}

export default List

子组件:

import { Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import type { TableRowSelection } from 'antd/es/table/interface';
import React, { useState } from 'react';

interface DataType {
  key: React.Key;
  name: string;
  age: number;
  address: string;
}

const columns: ColumnsType<DataType> = [
  {
    title: 'Name',
    dataIndex: 'name',
  },
  {
    title: 'Age',
    dataIndex: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
  },
];

const data: DataType[] = [];
for (let i = 0; i < 46; i++) {
  data.push({
    key: i,
    name: `Edward King ${i}`,
    age: 32,
    address: `London, Park Lane no. ${i}`,
  });
}

const TableList: React.FC = () => {
  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);

  const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
    console.log('selectedRowKeys changed: ', newSelectedRowKeys);
    setSelectedRowKeys(newSelectedRowKeys);
  };

  const rowSelection: TableRowSelection<DataType> = {
    selectedRowKeys,
    onChange: onSelectChange,
    selections: [
      Table.SELECTION_ALL,
      Table.SELECTION_INVERT,
      Table.SELECTION_NONE,
    ],
  };

  return <Table rowSelection={rowSelection} columns={columns} dataSource={data} />;
};

export default TableList;

截屏2022-10-22 10.38.42.png

子 ---> 父

要想实现子传父,首先我们需要在父组件中定义一个函数来接收数据

const exportData = (value) => {
  console.log(value)
}

然后,将此函数写在子组件作为一个属性,

<TableList exportData={exportData} />

下一步,我们来让子组件接收此函数,首先我们需要定义类型(因为写的是typescript)

interface IProps {
  exportData: (value) => void
}

还要改造一下子组件

const TableList: React.FC<IProps> = (props: IProps) => {
  const { exportData } = props || {}
  ......
};

export default TableList;

然后我们就可以在 onSelectChange 里面传数据了

const onSelectChange = (newSelectedRowKeys: React.Key[], selectedRows:DataType[]) => {
   console.log('selectedRowKeys changed: ', newSelectedRowKeys);
   setSelectedRowKeys(newSelectedRowKeys);
   exportData(selectedRows)
};

截屏2022-10-22 10.55.00.png

至此我们已经实现了 子 ---> 父 通信

(ps:这里我们需要注意一下,checkbox 选择与否是通过 selectedRowKeys 来控制的,不是通过 selectedRows

父 ---> 子

实现父传子很简单,上面其实我们已经介绍过了,通过 props 传给子组件

问题

要想实现点击父组件的按钮,触发子组件的处理逻辑,我原来的想法是父组件点击不同的按钮,传一个 selectType 值给子组件,子组件收到之后,根据不同的值去做数据更新,

// 子组件
React.useEffect(() => {
    switch (selectType) {
      case 'Select All':
        ...
        break
      case 'Deselect All':
        ...
        break
      case 'Select Invert':
        ...
        break
    }
  }, [selectType])

后来自己自测的实时候发现了问题,如果上一次点击了 invert,这次再点击 invert ,按理说应该会做两次数据调整,而页面却仅做了一次调整,问题就在于useEffect的参数,selectType的值没有改变,就不会去匹配,也就不会做数据处理了。

昨天想了一会了,得到了解决思路,如果子组件不能按预期更新,我可以在父组件把数据处理了之后再传给子组件,不就解决了子组件数据问题的了。(gif画质有点糊...)

tetrrr.gif

完整代码为:

子组件

import { Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import type { TableRowSelection } from 'antd/es/table/interface';
import React, { useState } from 'react';

interface DataType {
  key: React.Key;
  name: string;
  age: number;
  address: string;
}

interface IProps {
  exportData: (keys: React.Key[], values: DataType[], allValues: DataType[]) => void
  lists: {
    keys: React.Key[]
    values: DataType[]
    allValues: DataType[]
  }
}

const columns: ColumnsType<DataType> = [
  {
    title: 'Name',
    dataIndex: 'name',
  },
  {
    title: 'Age',
    dataIndex: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
  },
];

const data: DataType[] = [];
for (let i = 0; i < 46; i++) {
  data.push({
    key: i,
    name: `Edward King ${i}`,
    age: 32,
    address: `London, Park Lane no. ${i}`,
  });
}

const TableList: React.FC<IProps> = (props: IProps) => {
  const { exportData, lists } = props || {}
  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);

  React.useEffect(() => {
    onSelectChange(lists.keys, lists.values)
  },[lists])

  const onSelectChange = (newSelectedRowKeys: React.Key[], selectedRows:DataType[]) => {
    setSelectedRowKeys(newSelectedRowKeys);
    exportData(newSelectedRowKeys,selectedRows, data)
  };

  const rowSelection: TableRowSelection<DataType> = {
    selectedRowKeys,
    onChange: onSelectChange,
    selections: [
      Table.SELECTION_ALL,
      Table.SELECTION_INVERT,
      Table.SELECTION_NONE,
    ],
  };

  return <Table rowSelection={rowSelection} columns={columns} dataSource={data} />;
};

export default TableList;

父组件

import * as React from 'react'
import TableList from 'src/components/TableList'
import { Button } from 'antd'

interface DataType {
  key: React.Key
  name: string
  age: number
  address: string
}

const List: React.FC = () => {
  const [selectedRowKeys, setSelectedRowKeys] = React.useState<React.Key[]>([])
  const [data, setData] = React.useState<DataType[]>([])
  const [selectedRows, setSelectedRows] = React.useState<DataType[]>([])
  const [lists, setLists] = React.useState<{
    keys: React.Key[]
    values: DataType[]
    allValues: DataType[]
  }>({
    keys: [],
    values: [],
    allValues: [],
  })
  const exportData = (
    keys: React.Key[],
    values: DataType[],
    allValues: DataType[]
  ) => {
    setSelectedRowKeys(keys)
    setSelectedRows(values)
    setData(allValues)
  }

  return (
    <div style={{ marginTop: '20px' }}>
      <TableList exportData={exportData} lists={lists} />
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          height: '40px',
          backgroundColor: 'white',
        }}
      >
        <div>
          <Button type="text">{selectedRows.length} selected</Button>
          <Button
            type="link"
            onClick={() => {
              setLists({
                keys: data?.map((item) => item.key),
                values: data,
                allValues: data,
              })
            }}
          >
            Select All
          </Button>
          <Button type="link" onClick={() => {
            setLists({
              keys: data?.filter((item) => !selectedRowKeys.includes(item.key)).map((item) => item.key),
              values: data?.filter((item) => !selectedRowKeys.includes(item.key)),
              allValues: data,
            })
          }}>Select Invert</Button>
          <Button type="link" onClick={() => {
            setLists({
              keys: [],
              values: [],
              allValues: data,
            })
          }}>Deselect All</Button>
        </div>
        <div>
          <Button type="primary">Export</Button>
        </div>
      </div>
    </div>
  )
}

export default List

css布局问题

截屏2022-10-22 11.08.52.png

平常我们写需求的时候,可能会遇到这样的需求,左边有一些按钮,右边有一些按钮,之前老是应该怎么实现这些布局?最开始的想法是用 float 或者 position ,可是觉得这样不太好,样式维护起来可能会比较头疼。昨天在网上看 flex 布局的时候,justifyContent: 'space-between' 解决了我的疑问

截屏2022-10-22 11.15.37.png

mac快捷键

功能快捷键
刷新command + R
定位到地址栏command + L
打开新的标签页command + T
打开新窗口command + N
打开历史记录command + Y

结语

如果有想法或者疑问,欢迎评论区留言