Author: Frost
Proofreading: Kangaroo Cloud Data Stack Front-end Team Operations Team
The article contains the following
Controlled and Uncontrolled Components
- uncontrolled components
- Controlled Components
- Controlled and uncontrolled component boundaries
- anti-pattern
- solution
foreword
In HTML, form elements ( <input>
/ <textarea>
/ <select>
), usually maintain state themselves and update them based on user input
<form>
<label>
名字:
<input type="text" name="name" />
</label>
<input type="submit" value="提交" />
</form>
In this HTML, we can arbitrarily enter the value in the input. If we need to get the content entered by the current input, what should we do?
Controlled and Uncontrolled Components
uncontrolled component
Using uncontrolled components, instead of writing data processing functions for each state update, the form data is handed over to the DOM node for processing, and Ref can be used to get the data
In an uncontrolled component, you want to be able to give the form an initial value, but not control subsequent updates. A default value can be specified using defaultValue
class Form extends Component {
handleSubmitClick = () => {
const name = this._name.value;
// do something with `name`
}
render() {
return (
<div>
<input
type="text"
defaultValue="Bob"
ref={input => this._name = input}
/>
<button onClick={this.handleSubmitClick}>Sign up</button>
</div>
);
}
}
controlled component
In React, mutable state is usually stored in the component's state property and can only be updated via setState
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'shuangxu'};
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value}/>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
In the above code, the value attribute value is set in Input, so the displayed value is always this.state.value
, which makes state the only data source.
const handleChange = (event) => {
this.setState({ value: event.target.value })
}
<input type="text" value={this.state.value} onChange={this.handleChange}/>
If we write the handleChange
method in the above example, then every keypress will execute the method and update the React state, so the value of the form will change with the user's input
React components control the operations of the form during user input and state is the only source of data. Form input elements whose values are controlled by React in this way are called controlled components
Controlled and uncontrolled component boundaries
uncontrolled components
The Input component only receives a default value of defaultValue
. When calling the Input component, you only need to pass a defaultValue
through props.
//组件
function Input({defaultValue}){
return <input defaultValue={defaultValue} />
}
//调用
function Demo(){
return <Input defaultValue='shuangxu' />
}
Controlled Components
The display and change of the value need to be controlled by state
and setState
, the component controls the state internally, and implements its own onChange method
//组件
function Input() {
const [value, setValue] = useState('shuangxu')
return <input value={value} onChange={e=>setValue(e.target.value)} />;
}
//调用
function Demo() {
return <Input />;
}
Is the Input component controlled or uncontrolled at this time? If we use the previous writing to change this component and its call
//组件
function Input({defaultValue}) {
const [value, setValue] = useState(defaultValue)
return <input value={value} onChange={e=>setValue(e.target.value)} />;
}
//调用
function Demo() {
return <Input defaultValue='shuangxu' />;
}
The Input component at this point is itself a controlled component, which is driven by unique state data. But for Demo, we do not have a right to change the data of the Input component, so for the Demo component, the Input component is an uncontrolled component. (‼️It is an anti-pattern to call controlled components in the way of uncontrolled components)
How to modify the current Input and Demo component codes so that the Input component itself is also a controlled component, and it is also controlled for the Demo component?
function Input({value, onChange}){
return <input value={value} onChange={onChange}
}
function Demo(){
const [value, setValue] = useState('shuangxu')
return <Input value={value} onChange={e => setValue(e.target.value)} />
Anti-pattern - calling controlled components in the same way as uncontrolled components
While controlled and uncontrolled are often used to point to form inputs, they can also be used to describe components with frequently updated data.
Through the boundary division of controlled and uncontrolled components in the previous section, we can simply classify them as:
- If you use props to pass in data, there is a corresponding data processing method, and the component is considered controllable by the parent
- The data is only stored in the state inside the component, the component is not controlled by the parent
⁉️ What is derived state
Simply put, if some data in a component's state comes from outside, that data is called derived state.
Most of the problems caused by using derived state are due to two reasons:
- Copy props directly to state
Update state if props and state are inconsistent
Copy prop directly to state
⁉️ Execution period of
getDerivedStateFromProps
andcomponentWillReceiveProps
- Both lifecycles are executed when the parent re-renders, regardless of props changes
Therefore, it is not safe to directly copy props to the state in the two methods, which will cause the state to not be rendered correctly.
class EmailInput extends React.Component { constructor(props) { super(props); this.state = { email: this.props.email //初始值为props中email }; } componentWillReceiveProps(nextProps) { this.setState({ email: nextProps.email }); //更新时,重新给state赋值 } handleChange = (e) => { this.setState({ email: e.target.value }); }; render() { const { email } = this.state; return <input value={email} onChange={this.handleChange} />; } }
Set the initial value from the props to the Input, and it will modify the state when the Input is input. But if the parent component is re-rendered, the value of the input box Input will be lost and become the default value of props
Even if we compare nextProps.email!==this.state.email
before reset still results in update
For the current small demo, you can use shouldComponentUpdate
to compare whether the email in props has been modified and then decide whether it needs to be re-rendered. But for practical applications, this way of handling is not feasible, a component will receive multiple props, and changes to any one prop will cause re-rendering and incorrect state reset. Add in inline functions and object props, and it gets harder and harder to create a fully reliable shouldComponentUpdate
. shouldComponentUpdate
This lifecycle is more for performance optimization than for dealing with derived state.
So far, explain why cannot directly copy prop to state . Thinking about another question, what if you just use the email property in props to update the component?
Modify state after props change
Following the above example, only use props.email
to update the component, which can prevent bugs caused by modifying the state
class EmailInput extends React.Component {
constructor(props) {
super(props);
this.state = {
email: this.props.email //初始值为props中email
};
}
componentWillReceiveProps(nextProps) {
if(nextProps.email !== this.props.email){
this.setState({ email: nextProps.email }); //email改变时,重新给state赋值
}
}
//...
}
Through this transformation, the component will only reassign the state when props.email changes. Is there any problem with this transformation?
In the following scenarios, when switching between two accounts with the same email, this input box will not be reset, because the prop value from the parent component has not changed.
view example
This scene is constructed and may be oddly designed, but mistakes like this are common. There are two solutions to this anti-pattern. The point is, any data, we must ensure that there is only one data source, and avoid copying it directly.
solution
Fully controllable components
Remove the state from the EmailInput component, use props directly to get the value, and pass control of the controlled component to the parent component.
function EmailInput(props){
return <input onChange={props.onChange} value={props.email}/>
}
If you want to save temporary values, the parent component needs to perform the save manually.
Uncontrolled component with key
Let the component store the temporary email state, the initial value of email is still accepted through prop, but the changed value has nothing to do with prop
function EmailInput(props){
const [email, setEmail] = useState(props.email)
return <input value={email} onChange={(e) => setEmail(e.target.value)}/>
}
In the previous example of switching accounts, in order to switch different values on different pages, you can use the React special property key
. When the key changes, React will create a new component instead of simply updating the existing one ( for more ). We often use the key value when rendering dynamic lists, which can also be used here.
<EmailInput
email={account.email}
key={account.id}
/>
Every time the id changes, EmailInput
is recreated and its state is reset to the most recent email value.
Alternative
Doing this with the key property resets the state of the entire component. You can observe the change of id at
getDerivedStateFromProps
andcomponentWillReceiveProps
, which is troublesome but feasible
view exampleclass EmailInput extends Component { state = { email: this.props.email, prevId: this.props.id }; componentWillReceiveProps(nextProps) { const { prevId } = this.state; if (nextProps.id !== prevId) { this.setState({ email: nextProps.email, prevId: nextProps.id }); } } // ... }
Reset an uncontrolled component using an instance method
There are just two ways, both in the case of having a unique identification value. If you also want to recreate the component when there is no suitablekey
value. The first is to generate a random or incremented value as thekey
value, the other is to use the example method to force a reset of the internal state
The parent component uses ref to call this method, Click to see the exampleclass EmailInput extends Component { state = { email: this.props.email }; resetEmailForNewUser(newEmail) { this.setState({ email: newEmail }); } // ... }
How do we choose
In our business development, try to choose controlled components and reduce the use of derived state. Excessive use of componentWillReceiveProps may lead to incomplete props judgment, but repeated rendering of infinite loop problems.
In component library development, such as Ant Design, both controlled and uncontrolled calling methods are open to users, allowing users to choose the corresponding calling method independently. For example, in the Form component, we often use getFieldDecorator and initialValue to define form items, but we don't care about the intermediate input process at all, and get all the form values through getFieldsValue or validateFields at the final submission, which is an uncontrolled calling method. Or, when we have only one Input, we can directly bind the value and onChange events, which is called in a controlled manner.
Summarize
In this article, the concepts of uncontrolled and controlled components are first introduced. For a controlled component, the component controls the process of user input and state is the only source of data for the controlled component.
Then, the problem of component invocation is introduced. For the component caller, whether the component provider is a controlled component. For the caller, the boundary between controlled and uncontrolled components depends on whether the current component has control over changes to the subcomponent's value.
It then introduces the anti-pattern usage of invoking a controlled component as an uncontrolled component, along with related examples. Don't copy props directly to state, use controlled components instead. For uncontrolled components, when you want to reset the state when the prop changes, you can choose the following ways:
- Suggestion: use the
key
property to reset all the initial state inside - Option 1: Change only some fields and observe the changes of special properties (properties with uniqueness)
- Option 2: Use ref to call instance method
Finally, a summary of how to choose a controlled component or an uncontrolled component.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。