学习过 React 哲学的同学应该知道,我们在思考如何拆分组件时,要以简化代码为目的,充分分析当前页面/组件的交互特性、结构特性、数据特性,来判断当前的页面应该如何拆分。
上一篇文章中,我们在不思考拆分的情况下,实现了任务列表的增删改查。但是结果是代码比较复杂,并不简洁。因此结合上面讲到的拆分依据,我们可以将该页面拆分为如下三个步骤
<header /> <List /> <Addition />
如果对于这个拆分结果感觉还比较懵的话,建议结合直播回放回顾一下我们的 React 哲学。
OK,接下来就是分析如何实现。
header 部分因为只是简单的结构与样式,没有额外的功能,比较简单,因此我们不用过多的考虑该部分。
List 组件为一个列表。很显然,它需要一个数组从外部传入作为数据源。除此之外,它的内部还具有删除与修改操作。而数据源又是从外部传入,因此我们需要对外提供两个方法来让外部响应内部的删除与修改操作
所以,List 组件的 props 类型声明如下
import {Job} from './index' interface ListProps { list: Job[], onRemove: (i: number) => any, onEditor: (i: number, value: Job) => any }
明确了 List 的 props,它的封装代码就很好写了
import {Job} from './index' interface ListProps { list: Job[], onRemove: (i: number) => any, onEditor: (i: number, value: Job) => any } export default function List(props: ListProps) { const {list, onRemove, onEditor} = props function troggleSelected(i: number) { const job = list[i] onEditor(i, { ...job, isSelected: !job.isSelected }) } return ( <div className="container"> <div className="task-header"> <div className="title">进行中的任务</div> <div className="right">...</div> </div> {list.map((job, i) => ( <div className="job-wrapper" key={job.id}> <div className="selected" onClick={() => troggleSelected(i)}> {job.isSelected && <div className="circle"></div>} </div> <div className="desc">{job.desc}</div> <div className="remove" onClick={() => onRemove(i)}>删除</div> </div> ))} </div> ) }
Addition 组件的思考稍微复杂一点。
编辑弹窗的交互行为属于 Addition 的内部行为,因此控制它显示隐藏的数据也可以为内部数据。对外只需要提供一个添加时的钩子函数,把内部数据传到父级去即可。
这样的表述难免有点枯燥,结合代码来理解比较合适。
代码如下:
import { useEffect, useRef, useState } from 'react'; import { randomId } from '../../utils'; import {Job} from './index' interface AdditionProps { onAdd: (job: Job) => any } export default function Addition(props: AdditionProps) { const [desc, setDesc] = useState('') const [show, setShow] = useState(false) const inputRef = useRef<HTMLInputElement>(null) const {onAdd} = props useEffect(() => { if (show) { inputRef.current?.focus() } }, [show]) function add() { onAdd({ id: randomId(), desc, isSelected: false, createTime: Date.now() }) } return ( <> {show ? ( <div className="dialog"> <input onChange={event => setDesc(event.target.value)} ref={inputRef} placeholder="请输入任务描述" onBlur={() => {setTimeout(() => { setShow(false) }, 0)}} /> <div className="create" onClick={add}>创建</div> </div> ) : <div className="add" onClick={() => setShow(true)}>新增</div>} </> ) }
这样,我们的主逻辑就变得非常简单,数据上只需要维护一个 jobs
即可。
import { useState } from 'react'; import logo from './logo.svg'; import Addition from './Addition' import List from './List' import './index.css'; export interface Job { desc: string, id: string, createTime: number, isSelected: boolean } function App() { const [jobs, setJobs] = useState<Array<Job>>([]) function remove(i: number) { jobs.splice(i, 1) setJobs([...jobs]) } function editor(i: number, job: Job) { jobs[i] = job setJobs([...jobs]) } return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1>React 知命境</h1> </header> <List list={jobs} onRemove={remove} onEditor={editor} /> <Addition onAdd={(job) => setJobs([...jobs, job])} /> </div> ); } export default App;
组件的拆分是一个简化代码的过程,在这个过程中,我们剔除了主逻辑中的所有干扰因素,让主代码变得非常简洁,极大提高了可维护性。
组件拆分是一个需要我们不断积累与思考的软技能。它是决定你代码质量高低的核心部分。