反应 - 内部功能组件
想象一个简单的表单,它呈现多个应用一些布局调整的自定义组件。
自定义组件有:
、
、
、
,
表单组件的 JSX 如下所示:
return (
<View style={globalStyles.flex}>
<View style={styles.inputsContainer}>
<UsernameInput
... // a lot of props
/>
<PasswordInput
... // a lot of props
/>
<DateTimePicker
... // a lot of props
/>
</View>
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
</View>
);
如果我们尝试将组件的 render 方法拆分为多个内部渲染方法会怎样?子渲染函数?
const LoginForm = ({ ... }) => {
...
/**
* Renders the inputs of the form.
*
* @returns {React.ReactElement} The inputs.
*/
const renderInputs = () => (
<View style={styles.inputsContainer}>
<UsernameInput
... // a lot of props
/>
<PasswordInput
... // a lot of props
/>
<DateTimePicker
... // a lot of props
/>
</View>
);
/**
* Renders the submit button.
*
* @returns {React.ReactElement} The footer of the form.
*/
const renderFooter = () => (
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
);
return (
{renderInputs()}
{renderFooter()}
);
};
这些辅助方法返回 JSX!它们不是简单的普通函数,它们是组件!我们正在每次渲染上重新创建组件!
为了解决这个问题,我所做的是将内部函数转换为内部记忆组件,如下所示:
/**
* Renders the submit button.
*
* @type {React.FC}
* @returns {React.ReactElement} The footer of the form.
*/
const Footer = useCallback(() => (
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
), [deps]);
return (
<View style={globalStyles.flex}
<Inputs />
<Footer />
</View>
);
这被认为是反模式吗?我的意思是,当没有理由在应用程序的全局范围内(在新模块中或组件外部)创建新组件时,我们是否应该避免拆分 JSX?
Imagine a simple form, which renders multiple custom components applying some layout tweaks.
The custom components are: <UsernameInput />
, <PasswordInput />
, <DateTimePicker />
, <FancyButton />
, <Checkbox />
And the JSX of the form component looks like:
return (
<View style={globalStyles.flex}>
<View style={styles.inputsContainer}>
<UsernameInput
... // a lot of props
/>
<PasswordInput
... // a lot of props
/>
<DateTimePicker
... // a lot of props
/>
</View>
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
</View>
);
What if we try to split the component's render method into multiple inner sub-render functions?
const LoginForm = ({ ... }) => {
...
/**
* Renders the inputs of the form.
*
* @returns {React.ReactElement} The inputs.
*/
const renderInputs = () => (
<View style={styles.inputsContainer}>
<UsernameInput
... // a lot of props
/>
<PasswordInput
... // a lot of props
/>
<DateTimePicker
... // a lot of props
/>
</View>
);
/**
* Renders the submit button.
*
* @returns {React.ReactElement} The footer of the form.
*/
const renderFooter = () => (
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
);
return (
{renderInputs()}
{renderFooter()}
);
};
Those helper methods returns JSX! They are not simple vanilla functions, they are components! WE ARE RECREATING COMPONENTS ON EACH RENDER!
In order to handle this, what I do is converting the inner functions to inner memoized components, like this:
/**
* Renders the submit button.
*
* @type {React.FC}
* @returns {React.ReactElement} The footer of the form.
*/
const Footer = useCallback(() => (
<View style={styles.footer}>
<Checkbox />
<FancyButton type="submit" onPress={...} />
</View>
), [deps]);
return (
<View style={globalStyles.flex}
<Inputs />
<Footer />
</View>
);
Is this considered an anti-pattern? I mean, should we avoid splitting the JSX when there is no reason to create a new component in the global scope of the app (in a new module or outside the component)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果您不将这些函数作为 props 提供给其他组件,而这些组件在 props 不改变时不重新渲染来优化其渲染,那么在这些函数上使用 useCallback 就没有意义。 (很多人误解了
useCallback
,这有点微妙。)您每次都仍然重新创建它们(您必须创建函数以将其传递给useCallback
),然后您就需要做更多的工作(处理deps
)来决定是使用新创建的函数还是之前创建的函数。这是没有目的的开销,而不是useCallback
的用途。 useCallback 的目的是创建稳定或尽可能稳定的函数引用,用于传递给子组件,如果您不更改子组件接收的函数,这些子组件可能能够避免重新渲染。相反,可以:
在模块中制作这些组件(或只是辅助函数,具体取决于)。 (如果它们仅由您的组件使用,则无需导出它们。)这样,它们仅创建一次(加载模块时)。
如果你仔细想想,这就像任何其他变得太大的函数一样:你把它分成几个部分,然后将这些部分放入你调用的更小的函数中。相同的概念。
或者
在第一次渲染时创建一次函数(当然确保它们不会关闭所需的任何内容;而是将其传递给它们),通过
useRef< 将结果存储在引用上的对象上/代码>:
这样,它们实际上只在安装时创建一次。
我强烈倾向于#1。
There's no point in using
useCallback
on those functions if you don't provide those functions as props to other components that optimize their rendering by not re-rendering when their props don't change. (Lots of people misunderstanduseCallback
, it's a bit subtle.) You're still recreating them every time (you have to create the function to pass it intouseCallback
), you're just then doing even more work (processingdeps
) to decide whether to use the newly-created function or the previously-created function. It's overhead without purpose, and not whatuseCallback
is for. The purpose ofuseCallback
is to create stable or stable-as-possible function references for passing to child components that may be able to avoid re-rendering if you don't change the functions they receive.Instead, either:
Make those components (or just helper functions, depending) in your module. (No need to export them if they're only used by your component.) That way, they're only created once (when the module is loaded).
If you think about it, this is like any other function that's gotten too big: You break it into parts, and put those parts into smaller functions you call. Same concept.
Or
Create the functions once, on the first render (ensuring of course that they don't close over anything they need; pass it to them instead), storing the result on an object on a ref via
useRef
:That way, they truly are created only once, on mount.
I would strongly lean toward #1.