Starting with mineblog
Introduction: It was the first time for me to write React after I joined the company. I found that the component system of React was slightly different from that of other frameworks, which also triggered my thinking and summary on the controllability of components.
Controllable components? Uncontrollable components?
Since the introduction of Component systems on the front end, one of the most common but often overlooked concepts is Controlled Component and Uncontrolled Component.
What are controllable and uncontrollable?
The official details what controllable and uncontrolled components are, although only for the value property of the input component. But for many third-party component libraries, a component has more than one piece of data under control. For example, Ant Design’s Select component, value and Open are both controllable data. If you make value controllable and open uncontrollable, is that a controllable component or an uncontrollable component?
So in a broad sense, using controlled/uncontrolled components is not really appropriate. It makes more sense to use controlled versus uncontrolled data. A component may have both controllable and uncontrollable data.
Controllable data means that the data of the component is controlled by the user. Uncontrolled data means that the data of a component is not controlled by the user but controlled by the component itself.
The reason why there is controllable and uncontrollable is mainly related to people’s strange psychology. If you compare a framework to a company, components to people, and relationships between components to superiors and subordinates. The expectation from superiors is that you can do your job on your own, but you can always follow my orders. It’s a contradiction in itself, letting go and trying to take full control. With a superior like that, the subordinate must go crazy.
Why distinguish?
In Vue, the distinction between the two is ignored. Let’s take a look at this example.
<input/>
Copy the code
Here is the simplest Input component. Consider the following usage scenarios:
-
If I only care about the final result, that is, the input value, and not the intermediate process, the easiest way is to use v-Model or myself in the change event to get the value and save it.
<input v-model="value"/> <! -- OR --> <input @change="change"/>Copy the code
This scenario is very common, Vue can do it well, and the results are as expected.
-
If I just care about the outcome, but I want an initial value. Simply pass in a static string via value, or a variable, because Vue props is unidirectional.
<input v-model="value"/> <! --> <input value="init string" @change="change"/> <input :value="initValue" @change="change"/>Copy the code
The third scenario is not quite the right one. If initValue is updated during user input, it overwrites the user’s data and does not trigger the change event.
-
I don’t just care about the outcome, I care about the process, and I need to control the process. For example, the input string is uppercase, or certain strings are locked. A skilled engineer can certainly write the following code.
<input v-model="value"/> <! --> <input :value="value" @change="change"/> <! -- Modify data in change -->Copy the code
But there are problems:
- Data changes are made after dom rendering, which means that no matter what you do, the input will jitter.
- If by the second method, what you happen to do is limit the length of the string, you write it like this
change(e) {this.value = e.target.slice(0, 10)}
The function will find no effect. This is because vue does not detect changes in value beyond 10 characters and does not update input.value.
The most important reason for this problem is that there is no good distinction between controllable and uncontrollable components. Let’s review some of the above code:
<input :value="value" @change="change"/>
Copy the code
What can you tell from this piece of code about the intent of the user using this component? Does he want to control the use of components or just set an initial value? You never know. If we humans don’t know, it’s impossible to know at the code level. So Vue turned a blind eye to this area. It’s easy for users to use,
Use an example to describe the simple: the superior make you to do a task, you inquired about the superior information about these tasks (props), and then you started working initialization (components), and you will report to the superiors in a while you work schedule (onChange), schedule according to your feedback, reasonable arrangement of other things. Everything seems to be perfect. But some superiors have a strong desire for control. When you submit your work progress, they will make blind changes to your work and then tell you to follow my instructions. I didn’t say I would accept your props. I also have a component state here. Should I use my own or yours?
For people, how to deal with the requirements of their superiors (props) and their own work (state) is the expression of a person’s emotional intelligence. This logic is in line with ordinary people’s idea, but for a computer, it has no emotional intelligence and cannot judge who should be listened to. To overcome this, you need a lot more judgment and processing, and for immutable values, you need to clear them and then nextTick them before you can start updating them inside the component.
Recently, after I joined the company, React was used, and I really understood it.
value? defaultValue? onChange?
You can skip this section if you are familiar with React controllable and uncontrollable components.
Let’s see how React handles this. Okay? Let’s take the three cases above:
- If I only care about the end result, which is the input value, I don’t care about the intermediate process
<input onChange={onChange}/> Copy the code
- If I just care about the outcome, but I want an initial value
<input defaultValue="init value" onChange={onChange}/> <input defaultValue={initValue} onChange={onChange}/> Copy the code
- I don’t just care about the outcome, I care about the process, and I need to control the process
<input value={value} onChange={onChange}/> Copy the code
After reading this paragraph, you will have a clear idea of which structures are controllable and which are not:
- If you have
value
Then it’s controlled data, used forevervalue
The value of the - Otherwise it belongs to uncontrolled data and is used internally by the component
value
And passdefaultValue
Setting defaults
The onChange event is triggered in any case.
React’s distinction between controllable and uncontrollable is actually quite reasonable for computers and makes the whole process very clear. Of course, there’s more than one way to set things up, and there are rules that you can follow, but a good design must have a clear line between controllable and uncontrolled Settings.
propName
In this. Props?
Now that we know the above concepts, how do we determine whether the current component is controllable or not at the code level?
According to the above logic:
const isControlled1 = 'value' in this.props // approval 1
const isControlled2 = !!this.props.value // approval 2
const isControlled3 = 'value' in this.props && this.props.value ! = =null && this.props.value ! = =undefined // approval 3
Copy the code
Let’s take a look at the above judgment methods, corresponding to the following templates (for third-party components) :
<Input value={inputValue} /> // Element 1, expectation controllable
<Input value="" /> // Element 2, expectation controllable
<Input /> // Element 3, expectations are out of control
<Input value={null} / >// Element 4, expectation??
Copy the code
The following table is available
Whether the controllable | approval 1 | approval 2 | approval 3 |
---|---|---|---|
element1 | true |
true |
true |
element2 | true |
false |
true |
element3 | false |
false |
false |
element4 | true |
false |
false |
And you can see at first glance that method two is actually incorrect, because he can’t distinguish between these two states very well, so he just passes it.
Eagle-eyed students will also see why element 4 expectations are not filled out. This is because there is an official rule that does not say this: When the value property is set, the component becomes a controllable component that prevents the user from modifying the content of the input. However, if you want to set a value prop and still allow the user to edit it, you can only set value to undefined or null.
Under this official rule, Element 4 is expected to be an uncontrollable component, meaning that Approval 3 fully meets the official definition. But this can lead to a blurring of the line between controllable and uncontrollable.
<Input value={inputValue} />
// If inputValue is string, what is the component state? What is the state if null?
Copy the code
Therefore, IN fact, I recommend approval 1, which is also used by ANTD. It doesn’t fit the official definition, but I think it fits people’s intuition about how to use components. Sixth sense, = escape =
Independence
With that in mind, we can draw a simple flow chart (for example, the Input component) :
The picture is a bit complicated. In simple terms, every time we need to obtain or update controllable data, we need to check the state of the current component and choose which function to call when the data has been updated from props or state based on the state. The direction of some arrows in the picture is not quite correct, and some details are not drawn, please forgive me.
That’s fine if we just add this one controllable property value, but what if we want to consider a lot of properties at once? For example, the Antd Select component has both value and open controllable properties, so the total code size increases in a linear fashion. This is clearly unacceptable.
So here I’ve introduced the Independence decorator to do that. The structure is as follows:
We can understand that a component that supports controllable and uncontrolled components can essentially be split into an internal presentation stateless controlled component and an external wrapper component that supports uncontrolled controlled components by wrapping (i.e., higher-order components).
There are several benefits to writing this way:
- The complexity of the component logic is reduced by simply bringing the component under control
- Any controlled component can be packaged as an uncontrolled component, especially for third-party components
- Component complexity is reduced and code redundancy is reduced
- Very easy to add and remove controlled properties by modifying the decorator
How to use it?
At present, I have simply implemented Independence decorator. The code is in BDMS-UI, mammoth open source component library of NetEase (under construction, components are incomplete, documents are incomplete, time is not enough, please look forward to it), and the code is here.
It follows the convention that if the property name is value, the defaultValue is defaultValue and the change event is onValueChange. You can use onChangeName to change the name of the change event and use defaultName to change the defaultName.
The easiest way to use it is through decorators, such as the Select component.
@Independence({
value: {
onChangeName: 'onChange'
},
open: {} // Use the default value
})
export default class Select extends Component {
// blahblah, you can write as a controlled component
}
Copy the code
Writing controllable and uncontrolled data has never been easier. In addition Independence also implements the function of forward ref.
But now the function is still relatively weak, need to pass the test of time, such as complete can be packaged into a library.
conclusion
This article briefly explains what is controllable and uncontrolled, and proposes a React solution.
These are just summaries based on my experience, welcome to exchange actively.