-
-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: #83、exit delay #84
Conversation
…on unmount handling
…ed Motion components
@@ -11,4 +11,5 @@ export interface AnimatePresenceProps { | |||
custom?: any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom
property is currently typed as any
, which can lead to potential issues with type safety and maintainability. It's recommended to replace any
with a more specific type or an interface that accurately describes the expected structure of custom
. This change would leverage TypeScript's type checking to prevent runtime errors and improve code quality.
const style = document.createElement('style') | ||
if (config.value.nonce) { | ||
style.nonce = config.value.nonce | ||
} | ||
styles.set(state, style) | ||
document.head.appendChild(style) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating and appending a new <style>
element for each component state can lead to performance degradation, especially in large applications. This approach increases the number of DOM elements significantly, which can slow down the page rendering and increase memory usage.
Recommendation: Consider using a single <style>
element managed by usePopLayout
or a similar context provider. This element could aggregate all dynamic styles, which would reduce the number of DOM manipulations and improve performance. Use class names or CSS variables to apply styles dynamically to the elements.
style.sheet.insertRule(` | ||
[data-motion-pop-id="${state.id}"] { | ||
position: absolute !important; | ||
width: ${size.width}px !important; | ||
height: ${size.height}px !important; | ||
top: ${size.top}px !important; | ||
left: ${size.left}px !important; | ||
} | ||
${x}px !important; | ||
} | ||
`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of insertRule
with template literals that include dynamic content (e.g., state.id
, size.width
, etc.) poses a risk of CSS injection if the values are not properly sanitized. This can lead to security vulnerabilities where malicious content could be executed.
Recommendation: Ensure all dynamic values inserted into CSS rules are properly sanitized to prevent CSS injection. Alternatively, consider safer methods to apply styles, such as setting styles directly on elements via JavaScript, which avoids the complexity and risks associated with dynamic CSS rules.
export * from './motion-config' | ||
export * from './reorder' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using export *
from modules (./motion-config
and ./reorder
) can lead to namespace pollution and makes it difficult to track where specific exports are coming from. This can complicate maintenance and debugging. Consider explicitly listing which members to export to improve code clarity and maintainability.
Recommended Change:
// Example for motion-config
export { specificConfig, anotherConfig } from './motion-config'
// Adjust for actual exported members
@@ -3,3 +3,4 @@ | |||
export * from './motion-config' | |||
export * from './reorder' | |||
export { default as RowValue } from './RowValue.vue' | |||
export { mountedStates } from '@/state' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of an absolute path (@/state
) for importing mountedStates
can lead to issues with module resolution, especially if the project's configuration changes or in different environments. Consider using relative paths or ensuring that the module resolution configuration is robust across different environments.
Recommended Change:
// If possible, convert to a relative path
export { mountedStates } from '../../state'
// Adjust the path as necessary based on the actual structure
if (shouldDelay) { | ||
Promise.resolve().then(unmountState) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of Promise.resolve().then(unmountState)
to delay the unmount operation introduces a potential issue where errors thrown in the unmountState
function are not caught, leading to unhandled promise rejections. This can cause silent failures in the application.
Recommendation: Wrap the unmountState
call within the promise chain with a .catch()
block to handle possible errors gracefully. For example:
Promise.resolve().then(unmountState).catch(error => console.error('Failed to unmount state:', error));
const unmountChild = (child: MotionState) => { | ||
child.unmount(true) | ||
child.children?.forEach(unmountChild) | ||
} | ||
Array.from(this.children).forEach(unmountChild) | ||
Array.from(this.children).reverse().forEach(unmountChild) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new function unmountChild
inside the unmount
function on each call can lead to increased memory usage, especially when unmounting a large number of child components. This is because each function creation results in a new closure environment.
Recommendation: Define unmountChild
as a private method of the MotionState
class. This way, the function is created once and reused, which is more memory efficient. For example:
private unmountChild(child: MotionState) {
child.unmount(true);
}
Then, use it in the unmount
method:
Array.from(this.children).reverse().forEach(this.unmountChild);
export function getOptions(options: $Transition, key: string): $Transition { | ||
return options[key as any] ? { ...options, ...options[key as any] } : { ...options } | ||
return options[key as any] ? { ...options, ...options[key as any], [key]: undefined } : { ...options } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function getOptions
uses object spreading in a potentially inefficient and unsafe manner. If options[key]
is a large object, spreading it can be costly in terms of performance. Additionally, the type casting key as any
could lead to runtime errors if key
is not a valid property, potentially accessing undefined properties without checks.
Recommendation:
- Consider checking if
key
is a valid property before accessing it to avoid runtime errors. - If performance is a concern and
options[key]
can be large, consider alternative methods to manipulate objects that do not involve spreading large objects.
@@ -49,7 +49,7 @@ | |||
} | |||
|
|||
export function getOptions(options: $Transition, key: string): $Transition { | |||
return options[key as any] ? { ...options, ...options[key as any] } : { ...options } | |||
return options[key as any] ? { ...options, ...options[key as any], [key]: undefined } : { ...options } | |||
} | |||
|
|||
export function isCssVar(name: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function isCssVar
uses optional chaining (name?.startsWith('--')
) which is unnecessary since name
is a required parameter of type string. This could lead to confusion about the function's expectations regarding its inputs and reduce code clarity.
Recommendation:
- Remove the optional chaining to enforce the expectation that
name
should always be a string, enhancing the clarity and maintainability of the function.
export function delay(fn: () => void) { | ||
return Promise.resolve().then(() => { | ||
fn() | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The delay
function lacks error handling for the function fn
that it executes. If fn
throws an error, it will lead to an unhandled promise rejection, which can cause runtime issues.
Recommended Solution:
Wrap the function call fn()
inside a try-catch
block to handle any potential errors gracefully. For example:
export function delay(fn: () => void) {
return Promise.resolve().then(() => {
try {
fn();
} catch (error) {
console.error('Error executing function:', error);
// Handle error appropriately
}
});
}
}) | ||
if (isAnimate) { | ||
this.animateUpdates({ | ||
isFallback: !isActive, | ||
isFallback: !isActive && name !== 'exit' && this.visualElement.isControllingVariants, | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursive call to setActive
for child components in line 234 does not handle potential errors that might occur in the child's setActive
method. This can lead to unhandled exceptions if a child component encounters an issue during its state update.
Recommendation: Consider adding error handling around the recursive call to ensure stability. For example:
this.visualElement.variantChildren?.forEach((child) => {
try {
((child as any).state as MotionState).setActive(name, isActive, false);
} catch (error) {
console.error('Error updating active state for child:', error);
}
});
fix #83 Variant children dont work with v-show
fix #82 exit delay not working