英文:
React: elegant way to send click events to child component?
问题
以下是代码的翻译部分:
Option 1: 让子组件管理状态,允许父组件通过prop和回调来影响状态。
const Page = () => {
const [shouldWeHideStep1, setShouldWeHideStep1] = useState(false)
const agreeToTerms = () => {
// 子组件中的 useEffect 捕获这个prop的变化,执行必要的操作,然后运行回调函数,将此状态变量重新设置为false。
// 这使我们能够反复执行相同的过程。
setShouldWeHideStep1(true)
}
return (
<div>
<Collapsible
openDefault={true}
title="Step1: Terms"
toggleClose={shouldWeHideStep1}
toggleCloseDone={() => {
setShouldWeHideStep1(false)
}}>
<div>
这里是条款: 等等...
<button onClick={agreeToTerms}>同意并进入下一步</button>
</div>
</Collapsible>
<Collapsible openDefault={false} title="Step2: Something">
<div>
这是另外一些内容...
</div>
</Collapsible>
</div>
)
}
const Collapsible = ({openDefault, toggleClose = false, toggleCloseDone = null}) => {
const [open, setOpen] = useState(openDefault)
useEffect(() => {
// 父组件向我们发送一次性“关闭”事件。
// 我们在这里执行关闭操作,更新本地状态,然后回调父组件的setState方法。
if(toggleClose === true){
setOpen(false)
if(toggleCloseDone){
toggleCloseDone()
}
}
}, [toggleClose])
const toggle = () => {
setOpen(!open)
}
return (
<div className={styles.collapsible}>
<div className={styles.collapsibleTitle} onClick={toggle}>
{title}
</div>
{open && (
<div className={styles.collapsibleContent}>
{children}
</div>
)}
</div>
)
}
Option 2: 从子组件中移除状态,全部在父组件中管理。
const Page = () => {
const [step1Open, setStep1Open] = useState(true)
const [step2Open, setStep2Open] = useState(false)
const agreeToTerms = () => {
setStep1Open(false)
setStep2Open(true)
}
return (
<div>
<Collapsible
open={step1Open}
title="Step1: Terms"
toggleCallback={() => {
setStep1Open(!step1Open)
}}>
<div>
这里是条款: 等等...
<button onClick={agreeToTerms}>同意并进入下一步</button>
</div>
</Collapsible>
<Collapsible
openDefault={false}
title="Step2: Something"
open={step2Open}
toggleCallback={() => {
setStep2Open(!step2Open)
}}>
<div>
这是另外一些内容...
</div>
</Collapsible>
</div>
)
}
const Collapsible = ({open, toggleCallback}) => {
return (
<div className={styles.collapsible}>
<div className={styles.collapsibleTitle} onClick={toggleCallback}>
{title}
</div>
{open && (
<div className={styles.collapsibleContent}>
{children}
</div>
)}
</div>
)
}
英文:
This sounds like an incredible basic question, I'm looking for guidance on the best pattern to follow for this problem.
An example: clicking a button an accordion will collapse it.
This seemed simple enough, just pass in an open
prop and have the parent manage state. However this fell apart when wanting to also manage that open
state from the child component (i.e. clicking to open/close the accordion title).
I've got two basic examples below:
Option 1: Let child manage state, allow parent to optionally impact that state with a prop and callback.
const Page = () => {
const [shouldWeHideStep1, setShouldWeHideStep1] = useState(false)
const agreeToTerms = () => {
// A useEffect in child component picks up this prop change, does what it needs to do, and then
// runs our callback which will set this state variable back to false.
// This allows us to repeat the same process over and over again.
setShouldWeHideStep1(true)
}
return (
<div>
<Collapsible
openDefault={true}
title="Step1: Terms"
toggleClose={shouldWeHideStep1}
toggleCloseDone={() => {
setShouldWeHideStep1(false)
}}>
<div>
Here are the terms: etc..
<button onClick={agreeToTerms}>Agree and go to next step</button>
</div>
</Collapsible>
<Collapsible openDefault={false} title="Step2: Something">
<div>
Here is something else...
</div>
</Collapsible>
</div>
)
}
const Collapsible = ({openDefault, toggleClose = false, toggleCloseDone = null}) => {
const [open, setOpen] = useState(openDefault)
useEffect(() => {
// Our parent component is sending us a one off "time to close" event.
// We do our close thing here, update local state, then we call back to the parent
// setState method to
if(toggleClose === true){
setOpen(false)
if(toggleCloseDone){
toggleCloseDone()
}
}
}, [toggleClose])
const toggle = () => {
setOpen(!open)
}
return (
<div className={styles.collapsible}>
<div className={styles.collapsibleTitle} onClick={toggle}>
{title}
</div>
{open && (
<div className={styles.collapsibleContent}>
{children}
</div>
)}
</div>
)
}
Option 2: Remove state from child, manage it all in parent component
const Page = () => {
const [step1Open, setStep1Open] = useState(true)
const [step2Open, setStep2Open] = useState(false)
const agreeToTerms = () => {
setStep1Open(false)
setStep2Open(true)
}
return (
<div>
<Collapsible
open={step1Open}
title="Step1: Terms"
toggleCallback={() => {
setStep1Open(!step1Open)
}}>
<div>
Here are the terms: etc..
<button onClick={agreeToTerms}>Agree and go to next step</button>
</div>
</Collapsible>
<Collapsible
openDefault={false}
title="Step2: Something"
open={step2Open}
toggleCallback={() => {
setStep2Open(!step2Open)
}}>
<div>
Here is something else...
</div>
</Collapsible>
</div>
)
}
const Collapsible = ({open, toggleCallback}) => {
return (
<div className={styles.collapsible}>
<div className={styles.collapsibleTitle} onClick={toggleCallback}>
{title}
</div>
{open && (
<div className={styles.collapsibleContent}>
{children}
</div>
)}
</div>
)
}
What is the preferred approach for this type of problem? There's likely many more ways than the above two as well.
答案1
得分: 1
代码部分不需要翻译,以下是代码外的翻译部分:
"It always depends, but from what I see that you're trying to do, the state has to be outside the Collapsible
component in order to allow one of the Collapsible
's children to modify the visibility."
"Option 1 just doesn't seem right to me since you still have to have state in the parent to track when the first Collapsible
should be hidden, and it is an odd way of managing the state."
"If I have to choose between option 1 and 2, I would pick #2 with a few modifications:
- Since you want to open the next collapsible when the previous one closes, using
useReducer
would be much better for doing that synchronization and it would also allow you to add more collapsibles without creating a new state for every collapsible - Remove unused prop
openDefault
sinceopen
already does that"
下面是代码中的一部分,我将保留原样:
const Collapsible = ({ open, toggleCallback, title, children }) => {
// remove -- used for testing purposes
const styles = {
collapsibleTitle: 'test',
title: 'test',
};
return (
<div className={styles.collapsible}>
<div className={styles.collapsibleTitle} onClick={toggleCallback}>
{title}
</div>
{open && <div className={styles.collapsibleContent}>{children}</div>}
</div>
);
};
export const Page = () => {
// Setting the initial visibility for all the collapsibles here
const initialCollapsiblesVisibility = {
step1: true,
step2: false,
};
// The reducer will allow us to synchronize the collapsible components depending
// the action that happens
function reducer(prev, action) {
switch (action.type) {
// just toggle step1
case 'toggle1': {
return {
...prev,
step1: !prev.step1,
};
}
// just toggle step2
case 'toggle2': {
return {
...prev,
step2: !prev.step2,
};
}
// When we agree to terms, open collapsible2
case 'agree1': {
return {
step1: false,
step2: true,
};
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
const [stepsVisibility, setStepsVisibility] = useReducer(reducer, initialCollapsiblesVisibility);
return (
<div>
<Collapsible
open={stepsVisibility.step1}
title="Step1: Terms"
toggleCallback={() => {
setStepsVisibility({ type: 'toggle1' });
}}
>
<div>
Here are the terms: etc..
<button onClick={() => setStepsVisibility({ type: 'agree1' })}>Agree and go to next step</button>
</div>
</Collapsible>
<Collapsible
openDefault={false}
title="Step2: Something"
open={stepsVisibility.step2}
toggleCallback={() => {
setStepsVisibility({ type: 'toggle2' });
}}
>
<div>Here is something else...</div>
</Collapsible>
</div>
);
};
希望这有助于您理解代码的功能和结构。
英文:
It always depends, but from what I see that you're trying to do, the state has to be outside the Collapsible
component in order to allow one of the Collapsible
's children to modify the visibility.
Option 1 just doesn't seem right to me since you still have to have state in the parent to track when the first Collapsible
should be hidden, and it is an odd way of managing the state.
If I have to choose between option 1 and 2, I would pick #2 with a few modifications:
- Since you want to open the next collapsible when the previous one closes, using
useReducer
would be much better for doing that synchronization and it would also allow you to add more collapsibles without creating a new state for every collapsible - Remove unused prop
openDefault
sinceopen
already does that
...
const Collapsible = ({ open, toggleCallback, title, children }) => {
// remove -- used for testing purposes
const styles = {
collapsibleTitle: 'test',
title: 'test',
};
return (
<div className={styles.collapsible}>
<div className={styles.collapsibleTitle} onClick={toggleCallback}>
{title}
</div>
{open && <div className={styles.collapsibleContent}>{children}</div>}
</div>
);
};
export const Page = () => {
// Setting the initial visibility for all the collapsibles here
const initialCollapsiblesVisibility = {
step1: true,
step2: false,
};
// The reducer will allow us to synchronize the collapsible components depending
// the action that happens
function reducer(prev, action) {
switch (action.type) {
// just toggle step1
case 'toggle1': {
return {
...prev,
step1: !prev.step1,
};
}
// just toggle step2
case 'toggle2': {
return {
...prev,
step2: !prev.step2,
};
}
// When we agree to terms, open collapsible2
case 'agree1': {
return {
step1: false,
step2: true,
};
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
const [stepsVisibility, setStepsVisibility] = useReducer(reducer, initialCollapsiblesVisibility);
return (
<div>
<Collapsible
open={stepsVisibility.step1}
title="Step1: Terms"
toggleCallback={() => {
setStepsVisibility({ type: 'toggle1' });
}}
>
<div>
Here are the terms: etc..
<button onClick={() => setStepsVisibility({ type: 'agree1' })}>Agree and go to next step</button>
</div>
</Collapsible>
<Collapsible
openDefault={false}
title="Step2: Something"
open={stepsVisibility.step2}
toggleCallback={() => {
setStepsVisibility({ type: 'toggle2' });
}}
>
<div>Here is something else...</div>
</Collapsible>
</div>
);
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论