Есть одна вещь, которую я не понимаю, как правильно делать в чистом React (без Redux и Co по многим причинам, которые я не хочу здесь объяснять), и мы можем упростить проблему, изучив фиктивный флажок, который мы масштабируем до целого меню конфигурации.
Я вижу 3 способа достижения этого, и все, кажется, имеют недостатки, когда вы хотите увеличить масштаб. Давайте посмотрим.
Флажок без состояния
const Checkbox = ({
onPress = () => {},
checked = false
}) => {
return <input type="checkbox" checked={checked} />
}
Теперь, если у меня есть полное меню конфигурации с несколькими флажками, это означает, что родитель должен иметь состояние, содержащее все состояния всех флажков, и предоставить все как реквизиты в меню. См .:
const Parent = () => {
const [isBox1Checked, checkBox1] = useState(false)
const [isBox2Checked, checkBox2] = useState(false)
const [isBox3Checked, checkBox3] = useState(false)
const [isBox4Checked, checkBox4] = useState(false)
const [isBox5Checked, checkBox5] = useState(false)
return <Menu isBox1Checked={isBox1Checked} checkBox1={checkBox1} ... />
}
Я мог бы использовать массив, но имейте в виду, что то, что здесь упрощено, поскольку флажки будут всеми видами параметров (числа, логические значения, строки и т. Д. c)
Меню будет выглядеть так:
const Menu = ({isBox1Checked, checkBox1, ... }) => {
return <div>
<Checkbox onPress={checkBox1} checked={isBox1Checked} />
<Checkbox onPress={checkBox2} checked={isBox2Checked} />
<Checkbox onPress={checkBox3} checked={isBox3Checked} />
<Checkbox onPress={checkBox4} checked={isBox4Checked} />
<Checkbox onPress={checkBox5} checked={isBox5Checked} />
</div>
}
Плюсы:
- Легко читать текущую конфигурацию, где это необходимо
Минусы:
- Множество глупых кодов, которые нужно написать, просто чтобы передать все реквизиты
- Техническое обслуживание и рефакто, вероятно, станут кошмаром .. .
Флажок с отслеживанием состояния
const Checkbox = ({
onChange = () => {},
initialValue = false
}) => {
const [checked, setChecked] = useState(initialValue)
const toggle = () => {
setChecked(!checked)
onChange(!checked)
}
return <input type="checkbox" checked={checked} onChange={toggle}/>
}
Здесь мы сохраняем состояние флажка внутри флажка, который становится владельцем правды. Родственники могут узнать об изменениях состояния с помощью метода onChange
.
К сожалению, компонент Parent и Menu будет выглядеть точно так же, как и предыдущие ...
Плюсы:
- Больше Компонент-ориентированный флажок, который лучше подходит для разделения проблем.
Минусы:
- A много глупого кода, который нужно написать, просто чтобы передать все реквизиты
- Обслуживание и рефакто, вероятно, станут кошмаром ...
- Дублирование данных, так как есть флажок внутри флажка Флажок и состояние для функциональности в Parent ...
- Может возникнуть путаница в предоставлении
initialValue
в качестве реквизита, который будет игнорироваться при обновлении, поскольку реальность находится в состоянии
Чтение состояния с помощью ссылок
const Checkbox = forwardRef(({
onChange = () => {},
initialValue = false
}, ref) => {
const [checked, setChecked] = useState(initialValue)
//We wait for the DOM to be rendered for ref.current to worth something.
useLayoutEffect(() => ref.current.isChecked = () => checked, [ref.current])
const toggle = () => {
setChecked(!checked)
onChange(!checked)
}
return <input ref={ref} type="checkbox" checked={checked} onChange={toggle}/>
})
На этот раз флажок становится истинным владельцем правды, и для чтения данных мы читаем их непосредственно внутри флажка. Родитель стал бы чем-то вроде этого:
const Parent = () => {
const menu = createRef()
const onConfigChanged = ({ name, value }) => //Do sth if sth changed
const isBox1Checked = menu.current ? menu.current.isChecked('box1') : false
//Do something with valueOfParam1
return <Menu ref={menu} onChange={onConfigChanged} />
}
const Menu = forwardRef({ onConfigChanged }, ref) => {
// We create the refs for all the configs that this menu will be in charge for
const configs = useRef({
box1: createRef(),
box2: createRef(),
box3: createRef(),
box4: createRef(),
box5: createRef(),
})
// We provide a method to read the state of each checkbox by accessing their ref.
if(ref.current) ref.current.isChecked = name => configs[name].current.isChecked
const onChanged = name => value => onConfigChanged({name, value})
return <div ref={ref}>
<Checkbox ref={configs.current.box1} onChange={onConfigChanged('box1')} />
<Checkbox ref={configs.current.box2} onChange={onConfigChanged('box2')} />
<Checkbox ref={configs.current.box3} onChange={onConfigChanged('box3')} />
<Checkbox ref={configs.current.box4} onChange={onConfigChanged('box4')} />
<Checkbox ref={configs.current.box5} onChange={onConfigChanged('box5')} />
</div>
})
Плюсы:
- 100% Ориентация на компоненты с разделением интересов
- Родитель Код упрощен, что помогает подчеркнуть их собственную ответственность, а не наводнять ее распределением состояний
Минусы:
- Предположительно анти-паттерн использования ссылок для доступа к дочерним методам
- Может возникнуть путаница в предоставлении
initialValue
в качестве реквизита, который будет игнорироваться при обновлении, поскольку реальность находится в состоянии - Текущая реализация Parent имеет недостаток так как он не рендерится при изменении конфигурации.
Так, как правильно это реализовать?