簡體   English   中英

p5.j​​s 和 React:當組件的道具之一更新時如何更新組件內的草圖

[英]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;
  1. 您的“視圖”組件需要在應插入 p5.js 畫布的位置呈現容器元素。
  2. 因為您將空數組傳遞給useEffect函數調用,所以您的View2效果沒有得到更新。
  3. 傳遞給組件函數的props對象永遠不會改變。 props改變時,一個新的props對象被傳遞給對組件函數的新調用。
  4. 當需要更新效果時,會調用它們的清理函數,然后再次調用初始化函數。 因此,在清理過程中移除草圖很重要。 事實上,您應該始終這樣做,因為如果您的整個組件被刪除,您希望在從 DOM 中刪除其畫布元素之前讓 p5.js 知道這一點。

這是一個工作示例:

 // 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 專家,但我認為答案是肯定的。 基本上,您需要執行以下操作:

  1. 創建一個新的狀態變量/設置方法來存儲和設置草圖本身。
  2. 在創建草圖的useEffect調用中:
    1. 僅在尚不存在的情況下創建草圖
    2. 創建草圖時,請使用 set 方法將其存儲在狀態變量中。
    3. 不要返回清理函數
  3. 添加另一個效果,該效果在存儲草圖參考的狀態變量更新時更新
    1. 這個效果不應該創建任何東西,它應該只返回一個清理函數
    2. 這個清理函數應該刪除草圖,如果它存在(使用前面提到的狀態變量)
  4. 在您的草圖創建函數中,創建一個局部變量來存儲來自props的初始輸入數據
  5. 向您的草圖對象添加一個函數(通過分配p.updateFn = ... )來更新所述局部變量
  6. 在創建草圖的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM