有时候层层传递的确有失优雅
如果你的项目父子组件传值太繁琐,用redux又觉得太重,考虑用context + useReducer吧
以下代码将会出现父、子、孙三级组件,运用 useContext + useReducer 的组合实现多层级的数据通信
基本的嵌套关系是这样的
<App>
<Description>
<DescriptionEdit />
</Description>
</App>
App-父组件
import React, { useReducer } from 'react';
import { useEffect } from 'react';
import { Description, ChangeRequest, VisitedLink, Team } from '@/components/AppDetails';
import { Col, Row, Space } from 'antd';
import { getAppItemDetails, getOcrListOfAppItemDetails } from '@/service/app';
import { useParams } from '@route/runtime';
export const UPDATE_DESCRIPTION = 'UPDATE_DESCRIPTION';
export const UPDATE_CHANGE_REQUEST = 'UPDATE_CHANGE_REQUEST';
function reducer(state, action) {
switch(action.type) {
case UPDATE_DESCRIPTION:
return { ...state, description: action.description };
case UPDATE_CHANGE_REQUEST:
return { ...state, changeRequest: action.changeRequest };
default:
return state;
}
}
export const AppContext = React.createContext({});
const App = () => {
const [data, dispatch] = useReducer(reducer, { description: {}, changeRequest: {} });
const { id } = useParams() as { id: string };
async function initApi() {
const res = await Promise.all([
getAppItemDetails(id),
getOcrListOfAppItemDetails({
softwareProject: id,
open: true,
size: 10
})
])
console.log('--- * res * --- : ', res);
if (res) {
dispatch({ type: UPDATE_DESCRIPTION, description: res[0] });
dispatch({ type: UPDATE_CHANGE_REQUEST, changeRequest: res[1] });
}
}
useEffect(() => {
initApi();
}, []);
return (
<AppContext.Provider value={{ data, dispatch }}>
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<Description title="urbanic-app-a" />
<Row gutter={16}>
<Col span={16}>
<ChangeRequest />
</Col>
<Col span={8}>
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<VisitedLink />
<Team />
</Space>
</Col>
</Row>
</Space>
</AppContext.Provider>
)
};
export default App;
App-子组件 在子组件中会控制内部Modal弹框的显隐,使用 useImperativeHandle + forwardRef 实现
import { Breadcrumb, Card, message } from 'antd';
import { StarFilled } from '@ant-design/icons';
import { useContext, useRef } from 'react';
import DescriptionEdit from './DescriptionEdit';
import copy from 'copy-to-clipboard';
import { GitLabIcon,RepoIcon,TechIcon } from '@/icons';
import { AppContext } from '@/pages/app/$id';
interface Props {
title: string;
}
const Description = (props: Props) => {
const { title } = props;
const editRef = useRef(null) as any;
const { data } = useContext(AppContext) as ContextType;
const { description } = data;
return (
<Card>
<div className="flex">
{/* 面包屑和编辑事件 */}
<Breadcrumb style={{ marginBottom: 10 }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>
<a href="">Apps</a>
</Breadcrumb.Item>
<Breadcrumb.Item>
<a href="">{ title }</a>
</Breadcrumb.Item>
</Breadcrumb>
<a onClick={() => editRef?.current?.setIsModalOpen(true)}>Edit</a>
</div>
<div>
{/* 左侧描述部分 */}
<div>
<div className="flexStart mb20">
<span style={{ marginRight: 4 }} className="title">{ title }</span>
{/* 星标的颜色取决于是否收藏 */}
<StarFilled style={{ color: '#1890ff', fontSize: 12, paddingTop: 3 }} />
</div>
<div className="mb20" style={{ width: '70%' }}>{description?.description}</div>
<div className="flexStart">
<div className="mr20 flexStart">
<div className="mr5"><RepoIcon/></div>
<a onClick={() => {
copy(description?.codeRepo);
message.success('复制成功');
}}>Git-repo</a>
</div>
<div className="mr20 flexStart">
<div className="mr5">
<GitLabIcon />
</div>
<a onClick={() => window.open(description?.codeHomePage)}>Gitlab</a>
</div>
<div className="mr20 flexStart">
<div className="mr5">{ <TechIcon /> }</div>
<a onClick={() => alert('跳转到应用列表页面')}>技术质量部门</a>
</div>
</div>
</div>
{/* 右侧图片 */}
<img src="" alt=""/>
</div>
<DescriptionEdit
ref={editRef}
title={title}
/>
</Card>
)
}
export default Description;
App-孙组件 孙组件内包含Modal框、编辑交互、reload数据的操作等
import { forwardRef, useImperativeHandle, useState, useContext } from 'react';
import { Modal, Form, Input, Button, message } from 'antd';
import { useParams } from '@ice/runtime';
import { AppContext, UPDATE_DESCRIPTION } from '@/pages/app/$id';
import { getAppItemDetails, updateAppItemDetails } from '@/service/app';
interface Props {
title: string;
}
const tailLayout = {
wrapperCol: { offset: 19 },
};
const DescriptionEdit = forwardRef((props: Props, ref) => {
const { title } = props;
const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm();
const { id } = useParams() as { id: string };
const { data, dispatch } = useContext(AppContext) as ContextType;
const { description } = data;
useImperativeHandle(ref, () => {
return {
setIsModalOpen
}
});
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = async (values: { codeRepo: string, description:string }) => {
const newData = {...description, codeRepo: values?.codeRepo, description: values?.description };
// --
const res = await updateAppItemDetails(id, newData);
if (res?.status == 200) {
const res = await getAppItemDetails(id);
dispatch({ type: UPDATE_DESCRIPTION, description: res });
} else {
message.error(res?.statusText);
}
// --
setIsModalOpen(false);
};
const onReset = () => {
form.resetFields();
setIsModalOpen(false);
};
return (
<Modal
width="50%"
title={title}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
footer={false}
>
{/* Gitlab地址 和 简介 是可编辑的 */}
<Form
form={form}
onFinish={onFinish}
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
name="DescriptionEdit"
initialValues={{
codeRepo: description?.codeRepo ?? 'initGitlab',
description: description?.description ?? 'initDescription'
}}
autoComplete="off"
>
<Form.Item
label="Description"
name="description"
rules={[{ required: true, message: 'Please input your description!' }]}
>
<Input.TextArea />
</Form.Item>
<Form.Item
label="Gitlab"
name="codeRepo"
rules={[{ required: true, message: 'Please input your gitlab!' }]}
>
<Input />
</Form.Item>
<Form.Item {...tailLayout}>
<Button style={{ marginRight: 10 }} type="primary" htmlType="submit">
确认
</Button>
<Button htmlType="button" onClick={onReset}>
取消
</Button>
</Form.Item>
</Form>
</Modal>
)
})
export default DescriptionEdit;