javascript - react-spring วิธีใช้ animated(Component)
ฉันกำลังพยายามแก้ไขปัญหาด้านประสิทธิภาพในตัวสไลเดอร์ภาพ และฉันได้พบว่าการใช้ animated.img ให้ประสิทธิภาพที่ดีกว่าการใช้ animated.div ที่มีส่วนประกอบของ React อยู่ภายใน
คอมโพเนนต์ของ React เห็นได้ชัดว่าไม่ได้วางไว้เฉยๆ เพื่อความสนุก แต่โชคดีที่ react-spring ให้คุณสร้างแอนิเมชันกับคอมโพเนนต์แบบกำหนดเองได้โดยการทำ
const AnimatedComponent = animated(Component)
ตามเอกสาร
แต่ฉันจะใช้มันยังไง? ฉันพยายามแล้ว แต่ TypeScript แค่ให้ข้อความที่ไม่ช่วยอะไรเกี่ยวกับ props ที่หายไปถึง 269 ประเภท.
แก้ไข เพิ่มข้อผิดพลาด
ข้อผิดพลาดของ TypeScript แสดงโดย VSCode แต่บางทีอาจไม่สำคัญ เนื่องจากฉันไม่รู้ว่าจะส่ง props อะไรเพื่อทำให้คอมโพเนนต์เคลื่อนไหว ฉันจึงไม่แปลกใจที่มันไม่ทำงาน แต่ข้อความข้อผิดพลาดนั้นก็ไม่ได้ช่วยในการระบุว่าฉันต้องทำอะไร
' is missing the following properties from type 'AnimatedProps<{ title: string | FluidValue<string, any>; id: string | FluidValue<string, any>; article?: { title: string; metaTitle: string; metaDescription: string; description: string; showRelatedArticles: boolean; elements: ({ ...; } | ... 4 more ... | { ...; })[]; } | null | undefined; ... 269 more ...; key?: st...': title, id, slot, animate, and 257 more.ts(2740)
ฉันได้ลบพร็อพบางส่วนของครั้งแรกออกไปเพราะฉันจำพวกมันจากคอมโพเนนต์ที่ฉันกำลังพยายามทำให้เคลื่อนไหวได้ และฉันรู้ว่าพวกมันมีอยู่
มีใครลองใช้สิ่งนี้แล้วหรือยัง? ตัวอย่างการใช้งานมันจะดีมาก
ฉันใช้เวอร์ชัน 9.0.0-rc.3 ของ react-spring ถ้านั่นสำคัญ
คำตอบ #1
พื้นหลัง
react-spring ไม่พึ่งพาการจับเวลา เหมือนกับ CSS transition API แต่จะทำการเปลี่ยนแปลงและแอนิเมชันจากมุมมองทางฟิสิกส์ เพื่อให้ได้ประสิทธิภาพที่ยอมรับได้ใน React มันจะข้าม React และทำการแก้ไขไปยังโหนด DOM ที่เกี่ยวข้องโดยตรง
คอมโพเนนต์แอนิเมชัน react-spring ปกติ
อย่างที่คุณอาจเห็น โหนด DOM ปกติทั้งหมดมีอยู่เป็นค่าที่เทียบเท่าของ react-spring ตัวอย่างเช่น animation.span, animation.div เป็นต้น... เหล่านี้ครอบองค์ประกอบ DOM พื้นฐานด้วยฟังก์ชันที่จำเป็นเพื่อให้ react-spring ทำงาน สิ่งที่ควรสังเกตที่นี่คือความละเอียดสองประการนี้:
- ฟังก์ชันของ
react-springถูกแนบเข้ากับโหนด DOM เดียว - เนื่องจากฟังก์ชันนี้ถูกแนบกับโหนด DOM พื้นฐาน จึงใช้เฉพาะพร็อพขององค์ประกอบ DOM พื้นฐานเท่านั้น
ข้อเท็จจริงทั้งสองนี้มีนัยสำหรับวิธีที่เราสามารถใช้คอมโพเนนต์ที่กำหนดเองซึ่งถูกห่อด้วย animated
คอมโพเนนต์ที่กำหนดเองของเรา
มาทำงานกับสถานการณ์ง่าย ๆ โดยใช้คอมโพเนนต์ฟังก์ชันของ React และ Typescript และดูว่าคุณสามารถแปลงมันเป็นคอมโพเนนต์ react-spring แบบกำหนดเองได้อย่างไร
สมมติว่าคุณมี div ที่คุณต้องการให้พื้นหลังเปลี่ยนสีเมื่อเปลี่ยนจากสีหนึ่งไปยังอีกสีหนึ่งหลังจากคลิกมัน
1. ไม่มี react-spring
วิธีการพื้นฐานจะเป็นดังนี้:
const Comp: FC = () => {
const [color, setColor] = useState<string>("green")
return (
<div
style={{
backgroundColor: color,
transition: "background-color 1s"
}}
onClick={ () => setColor(color => color === "blue" ? "green" : "blue") }
/>
)
}
2. การใช้งานพื้นฐานของ react-spring
การทำสิ่งเดียวกันกับ react-spring การใช้งานพื้นฐานของ useSpring จะได้ผลลัพธ์เป็น
const Comp: FC = () => {
const [color, setColor] = useState<string>("green")
const springColor = useSpring({ backgroundColor: color})
return (
<animated.div
style={springColor}
onClick={ () => setColor(color => color === "blue" ? "green" : "blue") }
/>
)
}
3. การใช้งาน react-spring ตามแนวทางปฏิบัติที่ดีที่สุด
ที่ดียิ่งกว่าคือการใช้ฟังก์ชันของ API เพื่อที่เราจะไม่ต้องเรนเดอร์คอมโพเนนต์ซ้ำทุกครั้งที่มีการเปลี่ยนสี เพื่อให้ชัดเจน เมื่อคุณใช้วิธีนี้ คุณจะไม่ได้เปลี่ยนแปลง prop ใด ๆ ที่ส่งไปยังคอมโพเนนต์ที่คุณต้องการอนิเมต ดังนั้นคุณสามารถเปลี่ยนสถานะของมันผ่าน API โดยไม่ต้องเรนเดอร์ซ้ำ ตราบใดที่ Comp เองไม่ถูกเรนเดอร์ซ้ำ
const Comp: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }))
return (
<animated.div
style={springColor}
onClick={ () => api.start({ backgroundColor: springColor.backgroundColor.goal === "blue" ? "green" : "blue" })}
/>
)
}
4. ด้วยการมอบหมายให้กับชั้นเรียน
ลองคิดดู คุณกำลังส่งคุณสมบัติบางอย่างที่ห่อหุ้มแล้วไปยังคอมโพเนนต์ animated เหล่านี้ คุณสมบัติเหล่านี้มีประเภท SpringValue<T> และสามารถสร้างขึ้นได้โดยใช้ new หรือผ่านตัวอย่างเช่น useSpring ขั้นตอนแรกของเราสำหรับการสร้างคอมโพเนนต์แบบกำหนดเองคือเพียงแค่ส่งคุณสมบัติเหล่านี้เป็น property ให้กับคอมโพเนนต์ที่มีคอมโพเนนต์ animated อยู่ภายใน:
export interface CompProps {
color: SpringValue<string>;
onChangeColor: () => void;
}
const Comp: FC<CompProps> = (props: CompProps) => {
return (
<animated.div
style={{ backgroundColor: props.color }}
onClick={props.onChangeColor}
/>
)
}
const Parent: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }));
return (
<Comp
color={springColor.backgroundColor}
onChangeColor={() => api.start({
backgroundColor: springColor.backgroundColor.goal === "blue" ? "green" : "blue"
})}
/>
)
}
5. กับคอมโพเนนต์กำหนดเองที่เรียบง่าย ถูกห่อด้วย animated
ตอนนี้เราพร้อมที่จะทำการแทนที่และห่อหุ้ม property ของเราใน animated แทนการใช้ element animated ของ native ภายใน component ของเรา
export interface CompProps {
style: CSSProperties;
onChangeColor: () => void;
}
const Comp: FC<CompProps> = (props: CompProps) => {
return (
<div
style={props.style}
onClick={props.onChangeColor}
/>
)
}
const WrappedComp: AnimatedComponent<FC<CompProps>> =
animated(Comp)
const Parent: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }));
return (
<WrappedComp
style={{ backgroundColor: springColor.backgroundColor }}
onChangeColor={() =>
api.start({
backgroundColor:
springColor.backgroundColor.goal === "blue" ? "green" : "blue"
})
}
/>
)
}
โปรดทราบว่า ขณะนี้คอมโพเนนต์ที่เราแรปดูเหมือนคอมโพเนนต์ปกติและไม่มีสัญญาณใด ๆ ว่ากำลังถูกใช้ร่วมกับ react-spring แต่อย่างไรก็ตาม ตามที่เราจะเห็น ยังมีข้อกำหนดเพิ่มเติมบางอย่างสำหรับมันเพื่อให้การรวมกับ react-spring ทำงานได้ตามที่ตั้งใจ โปรดสังเกตว่าเราไม่ได้ส่ง backgroundColor เป็น prop อีกต่อไป แต่เราใช้ style แทน นอกจากนี้ prop ที่แอนิเมตทั้งหมดที่ส่งไปยังคอมโพเนนต์ที่กำหนดเองของเราคือ prop ที่สามารถใช้ได้กับองค์ประกอบ div พื้นฐานที่คอมโพเนนต์ที่กำหนดเองของเราผูกกับ forwarded ref ของมัน เพิ่มเติมเกี่ยวกับเรื่องนี้จะมีต่อไปด้านล่าง
ส่วนประกอบข้างต้นเป็นแบบง่าย ๆ เพราะว่า animated wrapper ไม่สามารถอัปเดตส่วนประกอบที่ห่อไว้โดยไม่ต้อง rerender ได้ ทำไมล่ะ? ง่าย ๆ เพราะส่วนประกอบที่เราห่อไว้นั้นไม่รองรับ ref ดังนั้นการเรียกร้องจาก react-spring ให้สามารถอัปเดตส่วนประกอบของเราโดยไม่ต้อง rerender จึงไม่สมเหตุสมผล
6. ด้วยคอมโพเนนต์กำหนดเองที่ถูกต้องซึ่งถูกห่อด้วย animated
มาลองพัฒนาคอมโพเนนต์ที่ห่อหุ้มของเราโดยให้มันรับ ref ได้
const Comp: FC<CompProps & RefAttributes<HTMLDivElement>> =
forwardRef<HTMLDivElement,CompProps>(
(props, ref) => {
return (
<div
ref={ref}
style={props.style}
onClick={props.onChangeColor}
/>
)
}
)
ตอนนี้ react-spring จะรับรู้ว่าคอมโพเนนต์ที่เราห่อรองรับ ref ดังนั้นมันจะงดการ rerender ทุกครั้งที่เกิดการเปลี่ยนแปลงและจะใช้ ref เพื่ออัปเดตแทน เนื่องจากการอัปเดตเกิดขึ้นผ่าน ref จึงสำคัญที่ props จะต้องเป็น props จริงบนองค์ประกอบที่เราต้องการเปลี่ยน; หากต้องใช้ React ในการแมป custom props ไปยัง props จริงบน DOM element เราจะต้อง rerender ทุกครั้งที่มีค่าแอนิเมชันใหม่ ซึ่งไม่เหมาะสม อย่างไรก็ตาม นี่คือสิ่งที่เกิดขึ้นพอดีเมื่อเราไม่เปิดให้คอมโพเนนต์ custom ของเรารองรับ ref ดังนั้น เวอร์ชัน 5 จะทำงานได้แม้ว่าเราจะยังคงใช้ props แบบกำหนดเอง backgroundColor แทน style อย่างไรก็ตาม เวอร์ชัน 6 จะไม่ทำงาน และคุณสมบัติ backgroundColor จะถูกเพิ่มเข้าไปในองค์ประกอบ DOM ที่มีการตั้งค่า ref ซึ่งจะไม่ทำให้เกิดการเปลี่ยนแปลงใด ๆ เนื่องจากคุณสมบัตินี้ไม่ใช่คุณสมบัติบนองค์ประกอบ DOM div พื้นฐาน
กล่องทราย
ฉันได้จัดทำกล่องทรายสองกล่องให้ใช้งานแล้ว:
แซนด์บ็อกซ์แรกแสดงส่วนประกอบในคำตอบนี้ แต่ละส่วนประกอบจะพิมพ์บนคอนโซลทุกครั้งที่มีการเรนเดอร์ ตรวจสอบการพิมพ์เหล่านี้และยืนยันพฤติกรรมที่อธิบายไว้ด้านบน แซนด์บ็อกซ์
แซนด์บ็อกซ์ที่สองมีธีมเดียวกันแต่ซับซ้อนขึ้นเล็กน้อย ที่นี่จำนวนครั้งในการเรนเดอร์ถูกอัปเดตอย่างต่อเนื่อง เพื่อให้เราสามารถตรวจสอบพฤติกรรมที่แตกต่างกัน สิ่งหนึ่งที่ได้เรียนรู้จากแซนด์บ็อกซ์นี้คือ การเปลี่ยนแปลงที่มีอนิเมชันทั้งหมดที่เราทำโดยใช้ API เป็น "ฟรี" ในแง่ที่ว่าไม่เพิ่มจำนวนครั้งในการเรนเดอร์ของคอมโพเนนต์ที่เกี่ยวข้อง เมื่อพาเรนต์ทำการเรนเดอร์ซ้ำ ตามปกติ ลูกทั้งหมดจะเรนเดอร์ซ้ำเช่นกัน รายการของประเด็นสำคัญได้ถูกเพิ่มไว้ด้านล่าง แซนด์บ็อกซ์
ข้อสรุป
- ควรใช้วิธีการของ API เสมอเมื่อใช้ hook ของ
react-spring - ทำให้ส่วนประกอบของคุณรับค่า ref ได้เสมอเมื่อห่อมันด้วย
animated. - หากคุณต้องการให้สามารถอัปเดตคอมโพเนนต์ที่ถูกห่อหุ้มโดยไม่ต้องเรนเดอร์ใหม่ คุณสามารถแนบพฤติกรรมของ
react-springกับองค์ประกอบได้เพียงตัวเดียวreact-springทำการอัปเดตโดยใช้ ref และเนื่องจากคุณไม่สามารถแนบ ref กับหลายองค์ประกอบพร้อมกันได้ (และforwardRefก็ไม่ได้ให้วิธีการเพิ่มหลาย ref) คุณจึงไม่สามารถใช้สปริงของคุณในหลายที่ภายในคอมโพเนนต์ที่ถูกห่อหุ้มโดยไม่ต้องเรนเดอร์ใหม่ มิฉะนั้นคุณจะไม่ได้ฟังก์ชันที่คาดหวัง สำหรับคอมโพเนนต์เช่นนี้ จะดีกว่าที่จะส่งSpringValue<T>เป็น props และใช้animatednative elements ภายในคอมโพเนนต์แทน
อย่า:
const NestedComp = ({ style1, style2 }) => {
...
return (
<div style={ style1 }>
<div style={ style2 }>
....
</div>
</div>
)
}
const Wrapped = animated(NestedComp)
ทำ :
const NestedComp = ({ springStyle1, springStyle2 }) => {
...
return (
<animated.div style={ springStyle1 }>
<animated.div style={ springStyle2 }>
....
</animated.div>
</animated.div>
)
}
- ตรวจสอบให้แน่ใจว่าคุณสมบัติแบบกำหนดเองที่คุณใช้ในอนิเมชันบนองค์ประกอบของคุณเข้ากันได้กับคุณสมบัติบนองค์ประกอบ DOM พื้นเมืองที่คุณแนบ ref มิฉะนั้น
react-springจะไม่สามารถอัปเดตองค์ประกอบโดยตรงได้อย่างถูกต้อง (แทนที่จะทำเพียงตั้งค่าคุณสมบัติซึ่งถ้ามันไม่มีอยู่บนองค์ประกอบ DOM จะไม่มีผลใด ๆ)
อย่า:
const Comp = ({ color }) => {
...
return (
<div style={{ backgroundColor: props.color }} />
)
}
const WrappedComp = animated(Comp)
const Parent = () => {
...
const [springProp, api] ) = useSpring(() => ({ color: "green" }))
...
return <WrappedComp color={ springProp.color } />
}
ทำ :
const Comp = ({ style }) => {
...
return (
<div style={props.style} />
)
}
const WrappedComp = animated(Comp)
const Parent = () => {
...
const [springProp, api] ) = useSpring(() => ({ color: "green" }))
...
return (
<WrappedComp
style={{ backgroundColor: springProp.color }}
/>
)
}
ในตัวอย่างข้างต้น เราต้องการอัปเดต Comp โดยไม่ใช้ React แต่ React จำเป็นในการประกอบ style prop ให้ถูกต้องเนื่องจาก color ไม่ใช่ prop พื้นฐานขององค์ประกอบ div ดังนั้นการอัปเดต color ผ่าน ref จะทำให้ได้เพียงการตั้งค่า color prop บน div ซึ่งจะไม่เกิดผลใด ๆ ในทางกลับกัน เมื่อเราเพิ่มคุณสมบัติ style ที่มีอยู่แล้วในพาเรนต์ องค์ประกอบ animated จะตรวจพบว่า หนึ่งใน style props เป็น SpringValue และการอัปเดตมันตามนั้นจะได้ผลลัพธ์ตามที่คาดไว้
สุดท้าย จำไว้ว่าถ้าเราไม่ได้ต้องการอัปเดตคอมโพเนนต์ที่กำหนดเองของเราด้วย API เราก็สามารถละเว้นจากการออกแบบคอมโพเนนต์ที่กำหนดเองของเราให้สามารถรับ ref และใช้ชื่อ prop ไหนก็ได้ที่เราต้องการ; react-spring จะทำการเรนเดอร์คอมโพเนนต์ใหม่ในทุกเฟรมของแอนิเมชันอยู่แล้ว ดังนั้น React จะทำการแมป prop ของคัสตอมทั้งหมดไปยัง prop ขององค์ประกอบ DOM ที่ถูกต้อง อย่างไรก็ตาม กลยุทธ์การดำเนินการนี้คงไม่เป็นที่ต้องการในความหวัง
คำตอบ #2
แค่เริ่มการสนทนาเท่านั้น
เริ่มต้นด้วยตัวอย่างจากเอกสาร สมมติว่าคุณมีคอมโพเนนต์ Donut ของบุคคลที่สาม มันมีคุณสมบัติ percent และคุณต้องการทำแอนิเมชันตามคุณสมบัตินี้ ดังนั้นคุณสามารถใช้ animated เป็นตัวห่อรอบ ๆ Donut ได้
const AnimatedDonut = animated(Donut)
// ...
const props = useSpring({ value: 100, from: { value: 0 } })
return <AnimatedDonut percent={props.value} />
มันคือที่ที่เกิดปัญหาหรือไม่?
