问题
I wrote a UI element as a function component which uses React's userReducer
hook and it seems to run without errors.
useReducer
references a function I wrote (called, imaginatively, reducer
):
const [state, dispatch] = React.useReducer(reducer, inputData,
(inputData) => initialState(inputData));
There's state
data which is input and output by the reducer function; and there are "managed" UI elements which depend on state
, something like ...
return (
<div>
<div>
{state.elements.map(getElement)}
</div>
<ShowHints hints={state.hints} inputValue={state.inputValue} />
</div>
);
... which is normal.
My concern is that the reducer
function isn't pure.
- Its behaviour depends only on its input parameters -- so calling it twice with the same input parameters has the same resulting effect.
- However it produces a side-effect, it doesn't only return new state
The side-effect is that there is an <input>
element whose state is controlled by one of these:
const inputRef = React.createRef<HTMLInputElement>();
The <input>
control is only semi-managed, something like this:
<input type="text" ref={inputRef} onKeyDown={handleKeyDown} onChange={handleChange}
The onKeyDown
and onChange
events are actions dispatched to the reducer (which is good) but the reducer is passed the HTMLInputElement
instance (i.e. the inputRef.current
value) as an input parameter, and the reducer sets properties of that HTMLInputElement
to mutate its state -- that is instead of the <input>
being a fully-managed component whose content is defined by the state that's output from the reducer.
The reason why the <input>
element isn't fully-managed is that I need to control the selection range (i.e. start
and end
) within the <input>
and not only its text value.
Questions:
- Is it OK (e.g. bug-free albeit questionable or remarkable) for a reducer function to be impure in this way?
- It does only depend on its input parameters and is therefore repeatable
- But it mutates something (i.e.
inputRef.current
properties) as well as returning newstate
value
- Is there another way for a reducer to control the
start
andend
properties of an<input type="text">
element, e.g. a way to define the<input>
element such that itsstart
andend
values are controlled by hestate
returned by a reducer?
(I think @Fyodor's answer below answers the second question, I'm still not sure about the first question).
What dictates the values that are to be set on the HTML element? Does the use information passed in or does it contain the logic?
The component's design and source code are shown here, and quite long.
It's a complicated "component" which is implemented using several elements -- a couple of <div>
s, several <span>
s, some clickable <svg>
s, and the <input>
element.
The reducer is given, as its input parameters:
- The previous "state"
- The current
<input>
instance (from which it can read the current state of the<input>
) - The "action" created by an event handler
Two of the several event handlers or actions are the onKeyDown
and onChange
events of the <input>
so the current state of the <input>
is passed-in to the reducer when there's an event which changes the state of the <input>
.
回答1:
Technically reducer may work with different side effects in it. I don't think it is a good practice, at least due to bad separation of concerns (it is expected that reducer only produce new state based on action, but not mutate something else). (Sorry for presenting my own opinion, as it seems, that side effects in useReducer
are used, like here). Also in Redux, all side effect are moved to action creators.
For your specific question I may recommend to move inputRef
mutation to separate useEffect
hook, which in turn will depends on state like below
useEffect (() => {
inputRef.currect // do work with inputRef
}, [state]); // make dependent from state
Here is sample of making useEffect
depended form state
You can also move useEffect
to custom hook to make code reusable like below (not tested, use as hint)
function mutateRef (inputRef: React.RefObject<HTMLInputElement>, state: /* type of state */) {
useEffect (() => {
inputRef.currect // do work with inputRef
}, [state, inputRef]);
}
来源:https://stackoverflow.com/questions/57142137/must-a-react-reducer-be-a-pure-function