[英]p5.js and React: how to update a sketch inside a component when one of the component's props is updated
我正在開發一個集成 p5.js 和 React 的項目。 我的項目由 App.js 和兩個子組件組成:View1.js 和 View2.js。 我想將信息從 View1 傳遞到 View2,並將這些變化的信息反映在 View 2 的草圖中。我的問題是,雖然我可以將數據從 View 1 傳遞到 View 2,但我不知道如何使用新值。
我認為部分問題可能是由於 View 2 組件中的草圖處於實例模式,因此一旦初始化,它就不會改變。 從本質上講,我正在尋找一種方法來重新初始化/刷新組件內的草圖,每次組件的一個道具的值發生變化時,草圖都會使用最新的值。 我知道 p5-react-wrapper 確實有處理這個問題的方法,但理想情況下,我只想用 p5 庫來做到這一點。
有誰知道這是怎么做到的嗎? 我在下面包含了一個我想要做的事情的例子(盡管在我的實際項目中它比這更復雜一些)。
應用程序.js
import React, { useState } from 'react';
import View1 from "./components/View1.js";
import View2 from './components/View2.js';
function App() {
const [data, setData] = useState(0);
const firstViewToParent = (num) => {setData(num);}
return (
<div className="App">
<View1
firstViewToParent={firstViewToParent}
/>
<View2
dataFromSibling={data}
/>
</div>
);
}
export default App;
視圖1.js
import React, { useEffect } from "react";
import p5 from 'p5';
const View1 = props => {
const Sketch = (p) => {
p.setup = () => {
p.createCanvas(400, 400);
}
p.draw = () => {
p.background(0);
}
p.mouseClicked = () => {
// Passing information from the child up to the parent
props.firstViewToParent(p.mouseX);
}
}
useEffect(() => {
new p5(Sketch);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<></>
);
}
export default View1;
視圖2.js
import React, { useEffect } from "react";
import p5 from 'p5';
const View2 = props => {
const Sketch = (p) => {
p.setup = () => {
p.createCanvas(400, 400);
}
p.draw = () => {
p.background(255, 0, 0);
// Want to be able to access updated information from sibling
// component inside the sketch
p.print(props.dataFromSibling);
}
}
useEffect(() => {
new p5(Sketch);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<></>
);
}
export default View2;
useEffect
函數調用,所以您的View2
效果沒有得到更新。props
對象永遠不會改變。 當props
改變時,一個新的props
對象被傳遞給對組件函數的新調用。這是一個工作示例:
// This is in place of an import statement const { useRef, useState, useEffect } = React; const View1 = props => { const containerRef = useRef(); const Sketch = (p) => { p.setup = () => { p.print("View1 Initializing"); p.createCanvas(200, 100); } p.draw = () => { p.background(150); } p.mouseClicked = () => { p.print('click!'); // Passing information from the child up to the parent props.firstViewToParent(p.map(p.mouseX, 0, p.width, 0, 255)); } } useEffect( () => { // make sure the p5.js canvas is a child of the component in the DOM new p5(Sketch, containerRef.current); }, // This empty list tells React that this effect never needs to get re-rendered! [] ); // Note: you're going to want to defined an element to contain the p5.js canvas return ( <div ref={containerRef}></div> ); } const View2 = props => { console.log('view2'); const containerRef = useRef(); const Sketch = (p) => { p.setup = () => { p.print("View2 Initializing"); p.createCanvas(200, 100); } p.draw = () => { p.background(props.dataFromSibling); } } useEffect( () => { let inst = new p5(Sketch, containerRef.current); // Cleanup function! Without this the new p5.js sketches // generated with each click will just appear one after the other. return () => inst.remove(); }, // Let React know that this effect needs re-rendering when the dataFromSibling prop changes [props.dataFromSibling] ); return ( <div ref={containerRef}></div> ); } function App() { const [data, setData] = useState(0); const firstViewToParent = (num) => { console.log('handling firstViewToParent callback'); setData(num); }; return ( <div className="App"> <View1 firstViewToParent={firstViewToParent}/> <View2 dataFromSibling={data} /> </div> ); } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
.App { display: flex; flex-direction: row; }
<html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script> </head> <body> <div id="root"></div> </body> </html>
所以我很好奇:如果您想更新使用useEffect()
創建的現有 p5.js 草圖而不刪除和重新創建整個 p5.js 草圖怎么辦。 好吧,免責聲明:我不是 ReactJS 專家,但我認為答案是肯定的。 基本上,您需要執行以下操作:
useEffect
調用中:
props
的初始輸入數據p.updateFn = ...
)來更新所述局部變量useEffect
調用中,如果草圖已經存在,則使用props
的新值調用所述更新函數。我知道有點復雜,但這里有一個例子:
// This is in place of an import statement const { useRef, useState, useEffect } = React; const View1 = props => { const containerRef = useRef(); const Sketch = (p) => { p.setup = () => { p.print("View1 Initializing"); p.createCanvas(200, 100); } p.draw = () => { p.background(150); } p.mouseClicked = (e) => { // It turns out that p5.js listens for clicks anywhere on the page! if (e.target.parentElement === containerRef.current) { p.print('click!'); // Passing information from the child up to the parent props.firstViewToParent(p.map(p.mouseX, 0, p.width, 0, 255)); } } } useEffect( () => { // make sure the p5.js canvas is a child of the component in the DOM let sketch = new p5(Sketch, containerRef.current); return () => sketch.remove(); }, // This empty list tells React that this effect never needs to get re-rendered! [] ); // Note: you're going to want to defined an element to contain the p5.js canvas return ( <div ref={containerRef}></div> ); } const View2 = props => { console.log('view2'); const containerRef = useRef(); const [sketch, setSketch] = useState(undefined); const Sketch = (p) => { let bgColor = props.dataFromSibling; p.setup = () => { p.print("View2 Initializing"); p.createCanvas(200, 100); } p.draw = () => { p.background(bgColor); } p.updateBackgroundColor = function(value) { bgColor = value; } } useEffect( () => { if (!sketch) { // Initialize sketch let inst = new p5(Sketch, containerRef.current); setSketch(inst); } else { // Update sketch sketch.updateBackgroundColor(props.dataFromSibling); } // We cannot return a cleanup function here, be cause it would run every time the dataFromSibling prop changed }, // Let React know that this effect needs re-rendering when the dataFromSibling prop changes [props.dataFromSibling] ); useEffect( () => { // This effect is only responsible for cleaning up after the previous one 😅 return () => { if (sketch) { console.log('removing sketch!'); // Removing p5.js sketch because the component is un-mounting sketch.remove(); } }; }, // This effect needs to be re-initialized *after* the sketch gets created [sketch] ); return ( <div ref={containerRef}></div> ); } function App() { const [data, setData] = useState(0); const [showSketch, setShowSketch] = useState(true); const firstViewToParent = (num) => { console.log('handling firstViewToParent callback'); setData(num); }; return ( <div className="App"> <View1 firstViewToParent={firstViewToParent}/> {showSketch && <View2 dataFromSibling={data} />} <button onClick={() => setShowSketch(showSketch ? false : true )}> Toggle </button> </div> ); } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
.App { display: flex; flex-direction: row; }
<html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script> </head> <body> <div id="root"></div> </body> </html>
如果這違反了任何 ReactJS 最佳實踐,或者在某些情況下會被破壞,希望比我更了解 React 的人會插話。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.