在不使用memo的情况下防止在React中添加项目时重新渲染整个列表



请考虑以下代码:https://codepen.io/kyxey/pen/XWEWBRY

const { useRef, useState } = React;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const addJob = () => {
const newJob = inputRef.current.value;
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{jobs.map((job) => {
const listItem = job + " " + Math.random();
return <li key={listItem}>{listItem}</li>;
})}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<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.js"></script>

在本例中,每当向列表中添加新项时,整个列表都会重新呈现。您可以通过查看每个项目前面的随机数来判断,该随机数将随着每个新项目添加到列表中而变化。

现在我知道这个问题有一些重复,我把它们都看了。但是他们都不能完全解决我的问题。

我的问题是:如何防止每次添加新项目时重新渲染整个列表,而不使用任何形状和形式的memouseMemo?这意味着每当向列表中添加新项时,只呈现新项,而列表中的其他项完全保持不变。同样,我不能使用memouseMemo来解决这个问题。

例如:

当前行为如下:

  • I在输入
  • 中输入Test
  • 点击Add按钮
  • 一个新项目被添加到下面的列表中,前面是一个随机数:
    • Test 0.8025874545033296
    
  • 输入
  • 输入AnotherTest
  • 点击Add按钮
  • 一个新项目被添加到下面的列表中,前面有一个随机数,但是第一个项目前面的随机数也被修改:
    • Test 0.4454662757698613
    • AnotherTest 0.16319305763152014
    

预期的行为应该是这样的:

  • I在输入
  • 中输入Test
  • 点击Add按钮
  • 一个新项目被添加到下面的列表中,前面是一个随机数:
    • Test 0.8025874545033296
    
  • 输入
  • 输入AnotherTest
  • 点击Add按钮
  • 一个新项目被添加到下面的列表中,在它前面有一个随机数,并且第一个项目前面的随机数不被修改。这意味着它不是单元格:
    • Test 0.8025874545033296
    • AnotherTest 0.16319305763152014
    

更新:

这个问题是我在一次编程面试中被问到的。他们明确提到我不允许使用memouseMemo,因为它们被认为是作弊!现在我不知道他们为什么会这样想,但我确信他们心中有一个具体的答案,那不是React应该如何表现。

你说:

这个问题是我在一次编程面试中被问到的。他们明确提到我不允许使用memouseMemo,因为这些被认为是作弊!现在我不知道他们为什么会这样想,但我确信他们心中有一个具体的答案,那不是React应该如何表现。

一个合理的答案——很可能是他们期待的答案——是这样的:"你可以这样做,但它只是重新发明memo没有很好的理由,通过做一些如此非标准和不寻常的事情,它会使代码难以理解和维护下一个人。如果他们真的想看到一个不使用这些东西的解决方案,我建议你把它们从你可能考虑工作的地方列表中划掉,如果你有任何选择的话(我尊重你可能没有选择的事实;我清楚地记得在我职业生涯的早期)。如果他们真的想要的不是被排挤(即使他们正在寻找被排挤),这是一个糟糕的面试问题,这可能表明这是一个糟糕的工作场所。

,从技术上讲,可以通过存储用ref呈现li元素(也许Map)。对我来说,这更像是"欺骗"。而不是像你应该做的那样做memo,但是……下面是一个例子:

const { useRef, useState } = React;
// *** A means of having unique keys for jobs
let nextJobId = 1;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const renderedJobs = useRef(new Map());
const jobLiElements = renderedJobs.current;
const addJob = () => {
// *** Make the jobs objects, not just strings, so the
// same string can be used by more than one job and
// so we can give the job a unique ID.
const newJob = {
id: nextJobId++,
value: inputRef.current.value,
};
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{jobs.map((job) => {
// *** Reuse li elements you already have if you have them,
// adding new ones if you don't
let li = jobLiElements.get(job);
if (!li) {
const listItem = job.value + " " + Math.random();
console.log(`Rendering li for ${job.value}`);
// *** You can't use `listItem` as the key, since there's
// _some_ chance of the same `li` having the same text and
// random number. Use the job object instead.
li = <li key={job.id}>{listItem}</li>;
jobLiElements.set(job, li);
}
return li;
})}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<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.js"></script>

但即使我走了这么远,向他们展示,我也会向他们展示一个使用memo的解决方案,并将随机值放在工作状态中,谈论这样做的优势(尤其是这是正常的,预期的方式):

const { useRef, useState } = React;
const JobItem = React.memo(({ job: { value, rand } }) => {
const text = `${value} ${rand}`;
console.log(`Rendering JobItem for "${text}"`);
return <li>{text}</li>;
});
// *** A means of having unique keys for jobs
let nextJobId = 1;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const addJob = () => {
// *** Make the jobs objects, not just strings, so the
// same string can be used by more than one job, and so
// we can assign it a unique ID to use as a key.
// *** Assign the random number once, as part of the job.
const newJob = {
id: nextJobId++,
value: inputRef.current.value,
rand: Math.random(),
};
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{/* *** Now just use JobItem*/}
{jobs.map((job) => (
<JobItem key={job.id} job={job} />
))}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<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.js"></script>