Framer Motion ReOrder组件排序异常问题求助
问题描述
我正在用Framer Motion实现可拖拽重排的列表,目标是做出流畅的拖拽过渡效果。项目基于Formik表单,ingredientList存储食材的create_index,对应values.ingredients数据。
目前核心问题:列表项拖拽时没有示例那样的流畅过渡,而是突兀切换——把第二个项拖到首位时,第一个项不会平滑下移,两者直接突然交换位置。
父组件代码
const IngredientTab = ({values}: any) => { const [ingredientList, setIngredientList] = useState(values.ingredients.map((ingredient) => ingredient.create_index)) return ( <div css={IngredientTabStyles}> <FieldArray name="ingredients"> {({ insert, remove, push }) => ( <div> <Reorder.Group axis="y" values={ingredientList} onReorder={setIngredientList}> <AnimatePresence> {ingredientList.map((ingredientUniqueValue, index) => { return ( <Ingredient key={index} index={index} uniqueValue={ingredientUniqueValue} ingredients={values.ingredients} order={`${ingredientList.indexOf(ingredientUniqueValue) + 1}`} /> ) })} </AnimatePresence> </Reorder.Group> <button type="button" className="add-ingredients" onClick={() => { setIngredientList([...ingredientList, values.ingredients.length]) push({ name: '', additional_info: '', quantity: '', unit_id: '', create_index: values.ingredients.length}) }} > Add Ingredient </button> </div> )} </FieldArray> </div> ) } export default IngredientTab const IngredientTabStyles = css` .ingredient-fields { margin-bottom: 2rem; background-color: white; } `
子组件代码
const Ingredient = ({ uniqueValue, ingredients, order, ingredient}: any) => { const y = useMotionValue(0); const boxShadow = useRaisedShadow(y); const ingredientIndex = ingredients.findIndex(ingredient => ingredient.create_index==uniqueValue) return ( <Reorder.Item initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} key={uniqueValue} value={uniqueValue} style={{boxShadow,y}} > {ingredient} <div className="ingredient-fields" css={IngredientStyles}> <div className="order"> <h6>{order}</h6> </div> <div className="ingredient-name"> <Field name={`ingredients.${ingredientIndex}.name`} type='text' placeholder="Ingredient" /> <Field name={`ingredients.${ingredientIndex}.additional_info`} type='text' placeholder="Description" /> </div> <Field name={`ingredients.${ingredientIndex}.quantity`} type='number' placeholder="Quantity" /> </div> </Reorder.Item> ) } export default Ingredient const IngredientStyles = css` display: flex; margin-bottom: 2rem; .order { display: flex; align-items: center; background-color: ${theme.components.grey}; padding: 1rem 2rem; margin-right: 2rem; border-radius: 0.4rem; } .ingredient-name { display: flex; } input { padding-bottom: 1rem; border: none; border-bottom: 1px solid ${theme.colors.lightGrey}; font-family: 'Source Sans Pro', sans-serif; font-weight: 300; } `
解决方案
关键问题分析
- 列表项key使用索引导致动画丢失:用
index作为key,重排时索引变化会触发组件销毁重建,破坏Framer Motion的动画状态。 - AnimatePresence干扰默认过渡:
Reorder.Group内置重排动画逻辑,外部包裹AnimatePresence会打断原有流畅过渡。 - key重复定义:子组件
Reorder.Item重复设置key,导致React渲染逻辑冲突。 - 序号计算冗余:用
indexOf遍历数组获取顺序,效率低且易出错。
修改后的父组件代码
const IngredientTab = ({values}: any) => { const [ingredientList, setIngredientList] = useState(values.ingredients.map((ingredient) => ingredient.create_index)) return ( <div css={IngredientTabStyles}> <FieldArray name="ingredients"> {({ insert, remove, push }) => ( <div> <Reorder.Group axis="y" values={ingredientList} onReorder={setIngredientList}> {ingredientList.map((ingredientUniqueValue, index) => { return ( <Ingredient key={ingredientUniqueValue} // 使用唯一值作为key,保证稳定性 index={index} uniqueValue={ingredientUniqueValue} ingredients={values.ingredients} order={`${index + 1}`} // 直接用循环索引生成序号 /> ) })} </Reorder.Group> <button type="button" className="add-ingredients" onClick={() => { const newIndex = values.ingredients.length setIngredientList([...ingredientList, newIndex]) push({ name: '', additional_info: '', quantity: '', unit_id: '', create_index: newIndex}) }} > Add Ingredient </button> </div> )} </FieldArray> </div> ) } export default IngredientTab const IngredientTabStyles = css` .ingredient-fields { margin-bottom: 2rem; background-color: white; } `
修改后的子组件代码
const Ingredient = ({ uniqueValue, ingredients, order}: any) => { const y = useMotionValue(0); const boxShadow = useRaisedShadow(y); const ingredientIndex = ingredients.findIndex(ingredient => ingredient.create_index === uniqueValue) return ( <Reorder.Item initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} value={uniqueValue} style={{boxShadow, y}} > <div className="ingredient-fields" css={IngredientStyles}> <div className="order"> <h6>{order}</h6> </div> <div className="ingredient-name"> <Field name={`ingredients.${ingredientIndex}.name`} type='text' placeholder="Ingredient" /> <Field name={`ingredients.${ingredientIndex}.additional_info`} type='text' placeholder="Description" /> </div> <Field name={`ingredients.${ingredientIndex}.quantity`} type='number' placeholder="Quantity" /> </div> </Reorder.Item> ) } export default Ingredient const IngredientStyles = css` display: flex; margin-bottom: 2rem; .order { display: flex; align-items: center; background-color: ${theme.components.grey}; padding: 1rem 2rem; margin-right: 2rem; border-radius: 0.4rem; } .ingredient-name { display: flex; } input { padding-bottom: 1rem; border: none; border-bottom: 1px solid ${theme.colors.lightGrey}; font-family: 'Source Sans Pro', sans-serif; font-weight: 300; } `
修改说明
- 替换列表项key为唯一的
ingredientUniqueValue,避免组件频繁重建,保留动画状态。 - 移除
AnimatePresence,让Reorder.Group的默认重排动画生效。 - 删除子组件
Reorder.Item上重复的key定义。 - 简化序号计算逻辑,直接使用循环索引生成当前排序的序号。
内容的提问来源于stack exchange,提问作者csch99
相关产品推荐
相关产品推荐

