我在React玩了几年了,在某些情况下仍然对安装/卸载机制感到困惑。
由于挂载/卸载是执行副作用的地方,所以我不希望随机调用它们。所以我需要弄清楚它们是如何工作的。据我目前所了解,当虚拟dom不存在于实际dom中时,往往会被卸载。但是,这似乎不是全部,我也无法对此进行推理
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
function App() {
const [count, setCount] = useState(0);
const Component = name => <TestMount name={name} />;
return (
<div className="App">
<h1>{count}</h1>
<Component name="one" />
{Component("two")}
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
第一个组件正在重新安装应用渲染,而第二个组件则没有?为什么会这样?
答案 0 :(得分:4)
Component
是每次呈现App
时的新功能,因此<Component name="one" />
也每次都重新安装,它们被视为不同的组件。
Component("two")
调用的结果为<TestMount name={"two"} />
,每次渲染TestMount
时App
保持不变,因此不会重新安装。
Component
对于它的用途而言是无效的组件,因为name
参数不是字符串,所以将name
字符串作为TestMount
属性传递给name
组件但是当Component
像<Component name="one" />
一样使用时,props对象。 name => <TestMount name={name} />
是 render函数,为了清晰起见,最好相应地命名为renderTestMount
,因为不应像Component("two")
那样直接调用组件。
如果假定函数可以互换地用作组件或呈现函数,则应将签名更改为({ name }) => <TestMount name={name} />
。
可以通过记住<Component name="one" />
来实现Component
的预期行为:
const Component = useCallback(({ name }) => <TestMount name={name} />, []);
但是,由于Component
不依赖于App
范围,因此正确的方法是在外部进行定义:
const Component = ({ name }) => <TestMount name={name} />;
function App() {...}
例如,这就是React Router Route
对组件和render function具有单独的component
和render
道具的原因。这样可以防止不必要地重新安装需要在当前作用域中动态定义的路由组件。
答案 1 :(得分:1)
React
与element
而非Component
一样聪明 Component是用于使用<>
操作创建元素的模板。就我的预期而言,<>
与OOP世界中的new
运算符非常相似。
每次调用render
方法(或功能组件)。新元素是使用<>
创建的,但是React
足够聪明,可以告诉渲染器之间创建的元素实际上是相同的,即它是以前创建的,并且可以在因为元素是由相同的组件创建的
但是,当用于生成元素的Component的标识发生更改时(即使这些组件看起来相同),React也会相信会有新的东西出现,因此它删除(卸载)前一个元素并添加(装载)新元素。因此,将调用componentDidMount
或componentWillUnmount
。
认为我们有一个Component
,当我们使用element
生成<Component />
时,react可以告诉相同的元素,因为它们是由相同的Component
生成的
但是,HOCComponent=()=><Component />; element= <HOCComponent />
每次生成element
时,都会使用不同的Component
。它实际上是动态构建的HOC。由于HOC是在render函数内部动态创建的,因此乍一看可能会造成混淆。
我从未找到有关上述想法的官方文件。但是下面的代码足以证明
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
let _Component;
function cacheComponent(C) {
if (C && !_Component) {
_Component = C;
}
return _Component || null;
}
const CacheComponent2 = once(({ name }) => <TestMount name={name} />, []);
function App() {
const [count, setCount] = useState(0);
// can be used as a HOC of TestMount or a plain function returnnung a react element
const Component = name => <TestMount name={name} />;
const CacheComponent1 = cacheComponent(Component);
const CacheComponent3 = useCallback(
({ name }) => <TestMount name={name} />,
[]
);
return (
<div className="App">
<h1>{count}</h1>
{/* used as HOC */}
<Component name="one" />
{/* used as function returnning the element */}
{Component("two")}
<CacheComponent1 name="three" />
<CacheComponent2 name="four" />
<CacheComponent3 name="five" />
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
上面的代码还提供了三种不同的方式来避免意外的安装/卸载。所有解决方案都是以某种方式缓存HOC的身份