51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

在React中,使用`useRef` 声明式地添加项目还是使用`state`更具性能优势?

英文:

In React what is more performant: adding items imperatively using useRef, or using state?

问题 {#heading}

Imagine a chat app with infinite scroll. New messages are added to the bottom of the app when they arrive, and when the user scrolls up the app will load the last 10 messages from the database.

The classic, React-y way to do this is to use state.

const chatAppState = () => {
    const[messages, setMessages] = useState<Message[]>([]);

    // some logic happens...

    const onNewMessage = async () => {
        // happens when a user sends a new message
        const newMsgs: Message[] = await getNewMessages(...)
        setMessages(newMsgs)
    }

    const onLoadPrevMsgs = () => {
        // happens when the user scrolls all the way up
        // loads the last 10 messages
        const newMsgs: Message[] = await getOldMessages(numOfDisplayedMsgs, numOfDisplayedMsgs+10)
        setMessages(newMsgs)

    }

    return(
        <div>
            messages.map(msg => <div>{msg.content}</div>)
        </div>
    )
}

However a drawback of this is that every time there is a new message, you must re-load everything displayed to the user from the database.

Another option is to useRef and imperatively add div elements:

const chatAppRef = () => {
    const chatField = useRef<HTMLDivElement>();
    const msgs = {} // ...

    const makeMsg = (msg: Message): HTMLDivElement => {
        // creates a new message div element
    }

    const onNewMsgs = (newMsg: Message) => {
        const msg: HTMLDivElement = makeMsg()        
        chatField.current?.appendChild(msg)
    }

    const onLoadPRevMsgs = (preMsgs: Message[]) => {
        const newMsgs: HTMLDivElement[] = [];
        preMsgs.forEach(msg => {
            newMsgs.push(makeMsg(msg))
        })

        chatField.current?.prepend(...newMsgs)
    }

    return(
        <div ref={chatField}>
            messages.map(msg => <div>{msg.content}</div>)
        </div>
    )
}

However it is "common wisdom" that doing imperative dom manipulation is an antipattern in react. I wonder though if it is warranted here since it puts less demand on the server? Which is the more performant option? 英文:

Imagine a chat app with infinite scroll. New messages are added to the bottom of the app when they arrive, and when the user scrolls up the app will load the last 10 messages from the database.

The classic, React-y way to do this is to use state.

const chatAppState = () =&gt; {
    const[messages, setMessages] = useState&lt;Message[]&gt;([]);

    // some logic happens...

    const onNewMessage = async () =&gt; {
        // happens when a user sends a new message
        const newMsgs : Message[] = await getNewMessages(...)
        setMessages(newMsgs)
    }

    const onLoadPrevMsgs() =&gt; {
        // happens when the user scrolls all the way up
        // loads the last 10 messages
        const newMsgs : Message[] = await getOldMessages(numOfDisplayedMsgs, numOfDisplayedMsgs+10)
        setMessages(newMsgs)

    }

    return(
        &lt;div&gt;
            messages.map(msg=&gt;&lt;div&gt;{msg.content}&lt;/div&gt;)
        &lt;/div&gt;
    )
}

However a drawback of this is that every time there is a new message, you must re-load everything displayed to the user from the database.

Another option is to useRef and imperatively add div elements:

const chatAppRef = () =&gt; {
    const chatField = useRef&lt;HTMLDivElement&gt;();
    const msgs = {} // ...

    const makeMsg = (msg : Message) : HTMLDivElement =&gt;{
        // creates a new message div element
    }

    const onNewMsgs = (newMsg : Message) =&gt; {
        const msg : HTMLDivElement = makeMsg()        
        chatField.current?.appendChild(msg)
    }

    const onLoadPRevMsgs = (preMsgs : Message[]) =&gt;{
        const newMsgs : HTMLDivElement[] = [];
        preMsgs.forEach(msg =&gt; {
            newMsgs.push(makeMsg(msg))
        })

        chatField.current?.prepend(...newMsgs)
    }

    return(
        &lt;div ref={chatField}&gt;
            messages.map(msg=&gt;&lt;div&gt;{msg.content}&lt;/div&gt;)
        &lt;/div&gt;
    )
}

However it is "common wisdom" that doing imperative dom manipulation is an antipattern in react. I wonder though if it is warranted here since it puts less demand on the server? Which is the more performant option?

答案1 {#1}

得分: 1

你应该保持你的组件具有状态 (这是 React 的主要优势之一!) 幸运的是,你不必从头开始重新加载消息列表。你可以结合这两种方法:

const chatAppState = () => {

  const [messages, setMessages] = useState<Message[]>([]);

  const onNewMsgs = (newMsg : Message) => {
    setMessages(messages => [...messages, newMsg]);
  }

  const onLoadPrevMsgs = (prevMsgs : Message[]) =>{
    setMessages(messages => [...prevMsgs, ...messages]);
  }

  return (
    <div>
        messages.map(msg=><div>{msg.content}</div>)
    </div>
  );
}

这样可以减少从服务器加载消息的需求。

请注意,某些时候,消息数组可能会变得太长,无法完全在客户端进行连接和呈现。为了解决这个问题,你可以将数组替换为一个双端队列数据结构,但你还需要改进渲染,只渲染视图中的消息子集。不过,这是一个完全不同的问题。 英文:

You should keep your component stateful (that's the main benefit of React, after all!) Fortunately, there is no reason you have to reload your message list from scratch. You can combine the two approaches:

const chatAppState = () =&gt; {

const \[messages, setMessages\] = useState\&lt;Message\[\]\&gt;(\[\]);


const onNewMsgs = (newMsg : Message) =\&gt; {
setMessages(messages =\&gt; \[...messages, newMsg\]);
}


const onLoadPrevMsgs = (prevMsgs : Message\[\]) =\&gt;{
setMessages(messages =\&gt; \[...prevMsgs, ...messages\]);
}

`return (
&lt;div&gt;
messages.map(msg=&gt;&lt;div&gt;{msg.content}&lt;/div&gt;)
&lt;/div&gt;
);
}
`

This minimizes the need to load messages from the server.

Note that at some point the message array might become too long to concatenate and render completely client-side. To remedy that, you could replace the Array with a deque data structure, but you would also need to improve your rendering to only render the subset of messages that are in view. That's a whole different problem however.


赞(1)
未经允许不得转载:工具盒子 » 在React中,使用`useRef` 声明式地添加项目还是使用`state`更具性能优势?