缓存如何对 ref 做出反应?
how cache react prop to ref?
我的组件有一个 onChange 属性,
我不想 onChange 触发 useEffect,
所以我想将 onChange 缓存到一个 ref,
下面是我的代码,不知道代码对不对。
const Component = ({onChange})=>{
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
const [state, setState] = useState();
useEffect(()=>{
onChangeRef.current();
},[state]);
//....some code
}
I don't want onChange fire useEffect
你不能完全完全你所展示的方式,因为当你的函数组件被调用时,它已经 在渲染过程中。它必须是 memo-ized 以防止使用“Higher Order Component”。您可以使用自定义 areEqual
回调通过 React.memo
just memo-izing,但您的组件将使用 [=18= 的陈旧副本] 道具。相反,让我们编写自己的简单 HOC,它提供稳定的 onChange
代替不稳定的 onChange
.
function withStableOnChange(WrappedComponent) {
let latestProps;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
const onChange = function(...args) {
return latestProps.onChange.apply(this, args);
};
// use `React.memo` to memo-ize the component:
return React.memo(
(props) => {
latestProps = props; // Need this for the first render to work
return <WrappedComponent {...props} onChange={onChange} />;
},
(prevProps, nextProps) => {
// Remember the latest props, since we may not render
latestProps = nextProps;
// Props "are equal" if all of them are the same other than `onChange`
const allPropNames = new Set([
...Object.keys(prevProps),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && prevProps[name] !== nextProps[name]) {
return false; // Not equal
}
}
return true; // Props are "equal"
}
);
}
这是一个活生生的例子。请注意,当您单击第一个('Using component "without wrapper"')中的 +
时,子组件会得到 re-rendered,因为父组件的 onChange
不稳定。但是,当您单击第二个 ('Using component "with wrapper"') 中的 +
时,child-component 不会得到 re-rendered 因为 withStableOnChange
memo-izes 它和为它提供 onChange
的稳定版本,而不是它收到的不稳定版本。
const { useState, useEffect } = React;
function withStableOnChange(WrappedComponent) {
let latestProps;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
const onChange = function(...args) {
return latestProps.onChange.apply(this, args);
};
// use `React.memo` to memo-ize the component:
return React.memo(
(props) => {
latestProps = props; // Need this for the first render to work
return <WrappedComponent {...props} onChange={onChange} />;
},
(prevProps, nextProps) => {
// Remember the latest props, since we may not render
latestProps = nextProps;
// Props "are equal" if all of them are the same other than `onChange`
const allPropNames = new Set([
...Object.keys(prevProps),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && prevProps[name] !== nextProps[name]) {
return false; // Not equal
}
}
return true; // Props are "equal"
}
);
}
const ComponentWithoutWrapper = ({onChange, componentType}) => {
const [state, setState] = useState("");
useEffect(() => {
onChange(state);
}, [state]);
console.log(`Re-rendering the component "${componentType}"`);
return (
<input type="text" value={state} onChange={({currentTarget: {value}}) => setState(value)} />
);
};
const ComponentWithWrapper = withStableOnChange(ComponentWithoutWrapper);
const Test = ({componentType, ChildComponent}) => {
const [counter, setCounter] = useState(0);
// This `onChange` changes on every render of `Example`
const onChange = (value) => {
// Using `counter` here to prove the up-to-date version of this function gets
// when `ComponentWithWrapper` is used.
console.log(`Got change event: ${value}, counter = ${counter}`);
};
return <div>
<div>Using component "{componentType}":</div>
<ChildComponent onChange={onChange} componentType={componentType} />
<div>
Example counter: {counter}{" "}
<input type="button" onClick={() => setCounter(c => c + 1)} value="+" />
</div>
</div>;
};
const App = () => {
// (Sadly, Stack Snippets don't support <>...</>)
return <React.Fragment>
<Test componentType="without wrapper" ChildComponent={ComponentWithoutWrapper} />
<Test componentType="with wrapper" ChildComponent={ComponentWithWrapper} />
</React.Fragment>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
withStableOnChange
只是一个例子。在真正的 HOC 中,您可能至少将 prop 的名称作为参数,甚至可能处理多个函数 props 而不是一个。
为了完整起见,这里有一个 withStableOnChange
的副本,它使用 class
组件而不是 React.memo
。两个版本都支持使用 class
组件或函数组件作为它们包装的组件,这纯粹是您使用函数组件还是 class
组件编写 HOC 的问题。
function withStableOnChange(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
const instance = this;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
this.onChange = function(...args) {
return instance.props.onChange.apply(this, args);
};
}
shouldComponentUpdate(nextProps, nextState) {
// Component should update if any prop other than `onChange` changed
const allPropNames = new Set([
...Object.keys(this.props),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && this.props[name] !== nextProps[name]) {
return true; // Component should update
}
}
return false; // No need for component to update
}
render() {
return <WrappedComponent {...this.props} onChange={this.onChange} />;
}
};
}
使用此版本 withStableOnChange
的实例:
const { useState, useEffect } = React;
function withStableOnChange(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
const instance = this;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
this.onChange = function(...args) {
return instance.props.onChange.apply(this, args);
};
}
shouldComponentUpdate(nextProps, nextState) {
// Component should update if any prop other than `onChange` changed
const allPropNames = new Set([
...Object.keys(this.props),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && this.props[name] !== nextProps[name]) {
return true; // Component should update
}
}
return false; // No need for component to update
}
render() {
return <WrappedComponent {...this.props} onChange={this.onChange} />;
}
};
}
const ComponentWithoutWrapper = ({onChange, componentType}) => {
const [state, setState] = useState("");
useEffect(() => {
onChange(state);
}, [state]);
console.log(`Re-rendering the component "${componentType}"`);
return (
<input type="text" value={state} onChange={({currentTarget: {value}}) => setState(value)} />
);
};
const ComponentWithWrapper = withStableOnChange(ComponentWithoutWrapper);
const Test = ({componentType, ChildComponent}) => {
const [counter, setCounter] = useState(0);
// This `onChange` changes on every render of `Example`
const onChange = (value) => {
// Using `counter` here to prove the up-to-date version of this function gets
// when `ComponentWithWrapper` is used.
console.log(`Got change event: ${value}, counter = ${counter}`);
};
return <div>
<div>Using component "{componentType}":</div>
<ChildComponent onChange={onChange} componentType={componentType} />
<div>
Example counter: {counter}{" "}
<input type="button" onClick={() => setCounter(c => c + 1)} value="+" />
</div>
</div>;
};
const App = () => {
// (Sadly, Stack Snippets don't support <>...</>)
return <React.Fragment>
<Test componentType="without wrapper" ChildComponent={ComponentWithoutWrapper} />
<Test componentType="with wrapper" ChildComponent={ComponentWithWrapper} />
</React.Fragment>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
我的组件有一个 onChange 属性,
我不想 onChange 触发 useEffect,
所以我想将 onChange 缓存到一个 ref,
下面是我的代码,不知道代码对不对。
const Component = ({onChange})=>{
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
const [state, setState] = useState();
useEffect(()=>{
onChangeRef.current();
},[state]);
//....some code
}
I don't want onChange fire useEffect
你不能完全完全你所展示的方式,因为当你的函数组件被调用时,它已经 在渲染过程中。它必须是 memo-ized 以防止使用“Higher Order Component”。您可以使用自定义 areEqual
回调通过 React.memo
just memo-izing,但您的组件将使用 [=18= 的陈旧副本] 道具。相反,让我们编写自己的简单 HOC,它提供稳定的 onChange
代替不稳定的 onChange
.
function withStableOnChange(WrappedComponent) {
let latestProps;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
const onChange = function(...args) {
return latestProps.onChange.apply(this, args);
};
// use `React.memo` to memo-ize the component:
return React.memo(
(props) => {
latestProps = props; // Need this for the first render to work
return <WrappedComponent {...props} onChange={onChange} />;
},
(prevProps, nextProps) => {
// Remember the latest props, since we may not render
latestProps = nextProps;
// Props "are equal" if all of them are the same other than `onChange`
const allPropNames = new Set([
...Object.keys(prevProps),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && prevProps[name] !== nextProps[name]) {
return false; // Not equal
}
}
return true; // Props are "equal"
}
);
}
这是一个活生生的例子。请注意,当您单击第一个('Using component "without wrapper"')中的 +
时,子组件会得到 re-rendered,因为父组件的 onChange
不稳定。但是,当您单击第二个 ('Using component "with wrapper"') 中的 +
时,child-component 不会得到 re-rendered 因为 withStableOnChange
memo-izes 它和为它提供 onChange
的稳定版本,而不是它收到的不稳定版本。
const { useState, useEffect } = React;
function withStableOnChange(WrappedComponent) {
let latestProps;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
const onChange = function(...args) {
return latestProps.onChange.apply(this, args);
};
// use `React.memo` to memo-ize the component:
return React.memo(
(props) => {
latestProps = props; // Need this for the first render to work
return <WrappedComponent {...props} onChange={onChange} />;
},
(prevProps, nextProps) => {
// Remember the latest props, since we may not render
latestProps = nextProps;
// Props "are equal" if all of them are the same other than `onChange`
const allPropNames = new Set([
...Object.keys(prevProps),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && prevProps[name] !== nextProps[name]) {
return false; // Not equal
}
}
return true; // Props are "equal"
}
);
}
const ComponentWithoutWrapper = ({onChange, componentType}) => {
const [state, setState] = useState("");
useEffect(() => {
onChange(state);
}, [state]);
console.log(`Re-rendering the component "${componentType}"`);
return (
<input type="text" value={state} onChange={({currentTarget: {value}}) => setState(value)} />
);
};
const ComponentWithWrapper = withStableOnChange(ComponentWithoutWrapper);
const Test = ({componentType, ChildComponent}) => {
const [counter, setCounter] = useState(0);
// This `onChange` changes on every render of `Example`
const onChange = (value) => {
// Using `counter` here to prove the up-to-date version of this function gets
// when `ComponentWithWrapper` is used.
console.log(`Got change event: ${value}, counter = ${counter}`);
};
return <div>
<div>Using component "{componentType}":</div>
<ChildComponent onChange={onChange} componentType={componentType} />
<div>
Example counter: {counter}{" "}
<input type="button" onClick={() => setCounter(c => c + 1)} value="+" />
</div>
</div>;
};
const App = () => {
// (Sadly, Stack Snippets don't support <>...</>)
return <React.Fragment>
<Test componentType="without wrapper" ChildComponent={ComponentWithoutWrapper} />
<Test componentType="with wrapper" ChildComponent={ComponentWithWrapper} />
</React.Fragment>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
withStableOnChange
只是一个例子。在真正的 HOC 中,您可能至少将 prop 的名称作为参数,甚至可能处理多个函数 props 而不是一个。
为了完整起见,这里有一个 withStableOnChange
的副本,它使用 class
组件而不是 React.memo
。两个版本都支持使用 class
组件或函数组件作为它们包装的组件,这纯粹是您使用函数组件还是 class
组件编写 HOC 的问题。
function withStableOnChange(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
const instance = this;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
this.onChange = function(...args) {
return instance.props.onChange.apply(this, args);
};
}
shouldComponentUpdate(nextProps, nextState) {
// Component should update if any prop other than `onChange` changed
const allPropNames = new Set([
...Object.keys(this.props),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && this.props[name] !== nextProps[name]) {
return true; // Component should update
}
}
return false; // No need for component to update
}
render() {
return <WrappedComponent {...this.props} onChange={this.onChange} />;
}
};
}
使用此版本 withStableOnChange
的实例:
const { useState, useEffect } = React;
function withStableOnChange(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
const instance = this;
// Our stable onChange function -- not an arrow function so that
// it can pass on the `this` it's called with
this.onChange = function(...args) {
return instance.props.onChange.apply(this, args);
};
}
shouldComponentUpdate(nextProps, nextState) {
// Component should update if any prop other than `onChange` changed
const allPropNames = new Set([
...Object.keys(this.props),
...Object.keys(nextProps),
]);
for (const name of allPropNames) {
if (name !== "onChange" && this.props[name] !== nextProps[name]) {
return true; // Component should update
}
}
return false; // No need for component to update
}
render() {
return <WrappedComponent {...this.props} onChange={this.onChange} />;
}
};
}
const ComponentWithoutWrapper = ({onChange, componentType}) => {
const [state, setState] = useState("");
useEffect(() => {
onChange(state);
}, [state]);
console.log(`Re-rendering the component "${componentType}"`);
return (
<input type="text" value={state} onChange={({currentTarget: {value}}) => setState(value)} />
);
};
const ComponentWithWrapper = withStableOnChange(ComponentWithoutWrapper);
const Test = ({componentType, ChildComponent}) => {
const [counter, setCounter] = useState(0);
// This `onChange` changes on every render of `Example`
const onChange = (value) => {
// Using `counter` here to prove the up-to-date version of this function gets
// when `ComponentWithWrapper` is used.
console.log(`Got change event: ${value}, counter = ${counter}`);
};
return <div>
<div>Using component "{componentType}":</div>
<ChildComponent onChange={onChange} componentType={componentType} />
<div>
Example counter: {counter}{" "}
<input type="button" onClick={() => setCounter(c => c + 1)} value="+" />
</div>
</div>;
};
const App = () => {
// (Sadly, Stack Snippets don't support <>...</>)
return <React.Fragment>
<Test componentType="without wrapper" ChildComponent={ComponentWithoutWrapper} />
<Test componentType="with wrapper" ChildComponent={ComponentWithWrapper} />
</React.Fragment>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>