好的组件设计和封装是一切的基础,基于这以上构建出的各种工程化方案全局状态管理,React.memo、React.useMemo、React.useCallback都不是必须的,他们保证的是即使没有做好设计也能保证项目的下限,但保证不了他的扩展性。
设计包含什么
▐ 基础组件/业务组件
课程:https://www.joyofreact.com/
const SubmitButton = (props) => {
return <buttton>{props.buttonText}</buttton>
}
const SubmitButton = (props) => {
return <buttton>{props.isAdd ? '新增' : '编辑'}</buttton>
}
-
扩展性问题。按上述设计,如果后续该业务需要增加草稿功能,可能会传入第二个状态,isDraft来定义保存草稿的文案,随着业务的发展,后续的业务增加都需要起码修改两个文件,外部的编辑页组件和这个提交按钮组件。
▐ 通用特性/定制特性
// 对于按钮来说,按钮的文本是一个通用的特性
// 无论文本内容是什么,都不会影响按钮本身的 UI 特性
const BaseButton = (props) => {
const { buttonText } = props;
return <button>{buttonText}</button>
}
const SubmitButton = (props) => {
const { buttonText, isAdd } = props;
return <BaseButton buttonText={isAdd ? '新增' : '编辑'} />
}
-
节点的通用特性是什么? -
节点的定制特性是什么? -
如何封装?
const NODE_MAP = {
start: StartNode,
llm: LLMNode,
end: EndNode
}
const CustomNode = (props) => {
const { type } = props;
const RenderNode = NODE_MAP[type];
return <BaseNode {...props}>
<RenderNode />
</BaseNode>
}
const BaseNode = (props) => {
const { children, data, ...commonProperties } = props;
const onNodeClick = () => {
// TODO
};
return cloneElement(children, { data });
}
▐ 状态定义
-
DDD举例
-
区分「UI 状态」 和「业务状态」。 -
业务状态的组织借鉴后端 DDD 的思想,将状态归于某一个具体的业务领域。
表单案例
▐ 状态的存储
小技巧
▐ 内容提升
// before
const Parent = () => {
const [name, setName] = useState('han');
return (
<div>
<Child name={name}></Child>
</div>
);
}
const Child = (props) => {
return (
<SubChild name={props.name}/>
);
}
const SubChild = ({ name }) => {
// TODO
}
// 我们注意到 Child 这个组件他只是透传了 name 字段给 SubChild,他本身并没有使用 name。
// 这在结构上会在后续扩展上造成影响,而且存在多层的情况下就会导致 参数透传地狱
// 我们如果需要进行优化,有哪几种方案呢,React 的优化说到底只有两个方案,组件层级和状态管理
// 1. 使用 Context
// 2. 使用第三方状态管理工具
// 3. 内容提升
// 前两种都是将状态提升到一个更高的纬度,是一种相对来讲绕过的方案。
// 他们也是很好的解决方案。
// 内容提升是改变组件层级来达到同样的目的。
// after
const Parent = () => {
const [name, setName] = useState('han');
return (
<div>
<Child>
<SubChild name={name} />
</Child>
</div>
);
}
const Child = ({ children }) => {
return (
{children}
);
}
const SubChild = ({ name }) => {
// TODO
}
▐ 控制子组件的最小权限
// bed
const Parent = () => {
const [state, setState] = useState({
name: 'han',
sex: 'man'
});
return (
<div>
<section>
name: {name}
</section>
<section>
sex: {sex}
</section>
<ChangeNameForm setState={setState} />
</div>
)
}
// good
const Parent = () => {
const [state, setState] = useState({
name: 'han',
sex: 'man'
});
const handleChangeName = (name) => {
setState({ ...state, name });
}
return (
<div>
<section>
name: {name}
</section>
<section>
sex: {sex}
</section>
<ChangeNameForm handleChangeName={handleChangeName} />
</div>
)
}
▐ memo相关
const Parent = (props) => {
return <div>
<Child name={props.name} />
<MemoChild age={props.age} />
</div>
}
const Child = (props) => {
// 父组件渲染,每次都会渲染
return <div>{props.name}</div>
}
const MemoChild = memo((props) => {
父组件的 name 属性不变的情况下,不会重渲染
return <div>{props.name}</div>
})
const Card = () => {
const [otherState, setOtherState] = useState(0);
const [outputText, setOutputText] = useState(
'大家好,这是一个在特定场景下必须需要使用 memo 的示例,欢迎大家评论沟通想法'
);
return (
<div
onMouseLeave={() => setOtherState((pre) => ++pre)}
style={{ background: '#ddd' }}
>
<OutputPanel text={outputText} />
</div>
);
};
const OutputPanel = (outputProps: { text: string }) => {
return (
<div>
<div>{outputProps.text}</div>
<div>唯一ID:{Math.floor(Math.random() * 100)}</div>
</div>
);
};
结语
-
根据「 通用特性/定制特性 」确定组件的通用级别。 -
将特性区分为 「 UI 特性 / 业务特性 」来确定组件层级和封装。 -
结合一些技巧来优化组件层级和状态存储的位置,优化性能表现。 -
还有许多的细节需要处理(Typescript 定义、props 定义、样式等)...
本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。