少即是多;简化你的 React 代码,让你的应用性能更强大——第一部分
在React开发中,我们开发者常常会忘记一条准则,但这条准则却永远不应该被遗忘:“少即是多”。它不仅仅是一句格言,更是一种思维方式、一种解决问题的方法,它应该影响你的设计。KISS原则已经存在了 60 多年,时至今日,它依然像半个多世纪前那样具有前瞻性。
作为开发者,我们应该避免过度设计和过度开发,即在只需较少工作量即可达到相同结果的情况下,却投入过多精力。这可以通过多种方式实现,例如将组件重构为更小的组件、降低组件输入/输出的复杂性,以及避免过多的处理和复杂的算法。
我们应该力求一切尽可能简单,但又不能过于简单。也就是说,我们要在不造成认知负荷的情况下尽可能高效地工作,而不是把工作简化到极致,反而增加工作量。这并非新手程序员独有的问题;我们都曾为了达成目标而选择过捷径。有时我们别无选择,有时是因为不知道更好的方法,有时则只是因为我们不想投入时间认真去做。
“少即是多”的理念适用于各种经验水平的开发者,而且应该践行。它必然会提升你的应用程序开发水平,改进你参与开发的应用程序,并帮助你更高效地工作。衡量开发者的最终目标不应该是代码行数,而应该是代码质量、错误率和返工率。
简化 React 组件
我们可以采取多种策略来简化组件,而无需对现有组件进行大刀阔斧的改造。每一种策略都将在不同的博客文章中进行介绍。
- 将状态与显示分离,这将有助于您的应用程序符合成熟的 MVC 规则。
- 将处理延迟到服务和自定义钩子
- 避免超负荷运转
useEffect和useState - 确定是否
redux真的redux-saga需要 - 创建更高阶的组件来连接组件之间的功能
- 将计算逻辑从组件中移到辅助函数中,并通过自定义钩子注入。
- 尽可能使用延迟加载和延迟行为
1. 将状态与显示分离,这将有助于您的应用程序符合成熟的 MVC 规则。
遵循MVC原则的传统应用程序设计,会将应用程序逻辑拆分为三个不同的组件:模型(Model)、视图(View)和控制器(Controller)。控制器负责处理用户进入和退出以及用户事件。模型负责响应用户数据的变化,而视图则始终反映模型的内容。
让我们来看一个简化常见 React 组件结构的例子:
const globalState = someStateTool();
const myComponent: React.FC<> = () => {
const [ myState, setMyState ] = useState<any>({});
const [ loaded, setLoaded ] = useState<boolean>(false);
useEffect(() => {
setTimeout(() => { setLoaded(true); }, 2500);
setTimeout(() => { globalState.set("foo", "bar")}, 5000);
}, [])
return loaded ? (<MySubComponent/>) : (<SpinnerComponent/>);
}
const mySubComponent: React.FC = () => {
const [ someState, setSomeState ] = useState<any>(null);
globalState.subscribeTo("someEvent", ev => setSomeState(ev.data));
const handleClick = () => globalState.set("foo", "bar");
return (
<div>
<button onClick={handleClick}>Some title</button>
</div>
<div>{someState.foo}</div>
)
}
每个组件都包含各自独立的功能。因此,它们并非纯粹的组件,而是独立且可互换的。这类组件本身就能响应各种用户输入行为和数据驱动事件。这通常会导致复杂性和耦合性的增加,即使不是直接体现在父组件上,而是体现在数据流、事件订阅以及其他数据和事件源上。
每个组件都需要进行大量的测试工作,因为它们都需要模拟各种服务和提供商,并处理行为和交互。
// Create a contract for the sub component
type SubComponentType = { foo: string, handleClick: () => void };
const globalState = someStateTool();
const myComponent: React.FC<> = () => {
const [ myState, setMyState ] = useState<any>({});
const [ loaded, setLoaded ] = useState<boolean>(false);
globalState.subscribeTo("someEvent", ev => setMyState(ev.data));
const handleClick = () => globalState.set("foo", "bar");
useEffect(() => {
setTimeout(() => { setLoaded(true); }, 2500);
setTimeout(() => { globalState.set("foo", "bar")}, 5000);
}, [])
return loaded ? (<MySubComponent foo={myState.foo} handleClick={handleClick}/>) : (<SpinnerComponent/>);
}
// Make sure our component adheres to the type contract
const mySubComponent: React.FC<SubComponentType> = ({ foo, handleClick }) => {
return (
<div>
<button onClick={handleClick}>Some title</button>
</div>
<div>{foo}</div>
)
};
我们甚至可以更进一步,将过渡组件分离成更高阶的组件,或者说是一个包装组件,根据状态渲染不同的组件。
type SubComponentType = { foo: string, handleClick: () => void };
const globalState = someStateTool();
const myComponentLoader: React.FC = () => {
const [ loaded, setLoaded ] = useState<boolean>(false);
useEffect(() => {
setTimeout(() => { setLoaded(true); }, 2500);
}, [])
return loaded ? (<MyComponent/>) : (<SpinnerComponent/>);
}
const myComponent: React.FC<> = () => {
const [ myState, setMyState ] = useState<any>({foo: globalState.get("foo")});
globalState.subscribeTo("someEvent", ev => setMyState(ev.data));
const handleClick = () => globalState.set("foo", "bar");
return <MySubComponent foo={myState.foo} handleClick={handleClick}/>;
}
const mySubComponent: React.FC<SubComponentType> = ({ foo, handleClick }) => {
return (
<div>
<button onClick={handleClick}>Some title</button>
</div>
<div>{foo}</div>
)
};
我们编写了更多行代码来表示相同的组件结构,但是:
- 将模型逻辑与视图逻辑分离
MySubComponent是一个纯函数;在相同的输入条件下,它应该始终产生相同的输出。MyComponent使用 Enzyme 之类的工具可以轻松进行测试——只需验证子组件是否已加载即可。- 所有加载逻辑都由一个顶层组件处理。可加载的组件可以根据需要进行切换。
敬请期待第二部分,我将在其中介绍将处理延迟到服务和自定义钩子。
文章来源:https://dev.to/jmitchell38488/less-is-more-simplify-your-react-code-to-super-power-your-applications-part-1-2ga2
