Skip to content

Commit 00559a1

Browse files
committed
Add RFC for prop aliases
1 parent 063ea9a commit 00559a1

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

active-rfcs/0000-prop-aliases.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
- Start Date: 2019-02-01
2+
- Target Major Version: 2.x
3+
- Implementation PR: (leave this empty)
4+
5+
# Summary
6+
7+
Allow aliasing props, to have different external and internal names (some languages call these "argument labels"):
8+
9+
```js
10+
props: {
11+
externalName: {
12+
as: 'internalName'
13+
}
14+
}
15+
```
16+
17+
The `as` nomenclature is of course open for bikeshedding.
18+
19+
# Basic example
20+
21+
In this example (which is a modified version of the one [in Vue's docs](https://vuejs.org/v2/guide/components-props.html)), the component has a `counter` prop as part of its external API. It's aliased as `initialCounter` within the component itself, to prevent clashing with its own `counter` data property:
22+
23+
```js
24+
export default {
25+
props: {
26+
counter: {
27+
as: 'initialCounter'
28+
}
29+
},
30+
data () {
31+
return {
32+
counter: this.initialCounter
33+
}
34+
}
35+
}
36+
```
37+
38+
Here's how the component would be consumed:
39+
40+
```html
41+
<the-component :counter="5" />
42+
```
43+
44+
# Motivation
45+
46+
Since props are not to be mutated, it is recommended to define a local data property that uses the prop as its initial value.
47+
48+
1. Here is one example from Vue's docs:
49+
50+
```js
51+
props: ['initialCounter'],
52+
data() {
53+
return {
54+
counter: this.initialCounter
55+
}
56+
}
57+
```
58+
59+
This makes sense within the component itself, but it feels wrong for it to affect the external API:
60+
61+
```html
62+
<the-component :initialCounter="5" />
63+
```
64+
65+
Aliasing the prop locally (as outlined above) would allow the consumer to use the simpler `counter` name for the prop:
66+
67+
```html
68+
<the-component :counter="5" />
69+
```
70+
71+
1. Here's another example from the docs:
72+
73+
```js
74+
props: ['size'],
75+
computed: {
76+
normalizedSize() {
77+
return this.size.trim().toLowerCase()
78+
}
79+
}
80+
```
81+
82+
This works, but now you have to always refer to the sanitized size as `normalizedSize` within your component.
83+
84+
Using an alias, we can change it to:
85+
86+
```js
87+
props: {
88+
size: {
89+
as: 'rawSize'
90+
}
91+
},
92+
computed: {
93+
size() {
94+
return this.rawSize.trim().toLowerCase()
95+
}
96+
}
97+
```
98+
99+
which would allow us to refer to the normalized size as just `size` within our component, without affecting the component's external API.
100+
101+
## Prior art
102+
103+
External argument labels is something that is supported in many different languages.
104+
105+
Here's how they work [in Swift](https://docs.swift.org/swift-book/LanguageGuide/Functions.html):
106+
107+
```swift
108+
func greet(person: String, from hometown: String) -> String {
109+
return "Hello \(person)! Glad you could visit from \(hometown)."
110+
}
111+
112+
greet(person: "Bill", from: "Cupertino")
113+
```
114+
115+
The second argument is externally named `from` and internally named `hometown`. This makes for very intuitive designs, as evidenced by many of Apple's library APIs using this convention.
116+
117+
In fact, JS itself also supports external argument labels (insofar as object destructuring is JS's support for named arguments). Here's the JS version of that Swift function:
118+
119+
```js
120+
function greet({ person, from: hometown }) {
121+
return `Hello ${person})! Glad you could visit from ${hometown}.`
122+
}
123+
124+
greet({ person: 'Bill', from: 'Cupertino' })
125+
```
126+
127+
# Detailed design
128+
129+
**Introducing a new `as` key to prop options.**
130+
131+
If a prop's options specifies an `as` key, its value will be used as the internal name for the prop:
132+
133+
```js
134+
props: {
135+
externalName: {
136+
as: 'internalName'
137+
}
138+
}
139+
```
140+
141+
If the `as` key is not specified, the internal name will be the same as the external name, as it has always been.
142+
143+
# Drawbacks
144+
145+
- The new syntax may be confusing to beginners. Seeing a property used in the component without seeing that property in either the original data object or the top-level of the `props` may be a little disconcering to newcomers.
146+
147+
# Alternatives
148+
149+
The alternative is to keep things as is, and always use the same prop name both internally and externally.
150+
151+
# Adoption strategy
152+
153+
Since this change is purely additive, there's nothing we would have to do to help with its adoption. It can be added to the docs, and people can use them wherever it makes sense in their projects.
154+
155+
# Unresolved questions
156+
157+
1. **Should the top-level key in the `props` object be the internal name or the external name?**
158+
159+
This proposal uses the top-level key as the external name, with `as` denoting its internal name:
160+
161+
```js
162+
props: {
163+
externalName: {
164+
as: 'internalName'
165+
}
166+
}
167+
```
168+
169+
This makes it easy for consumers of the component to see at a glance which props they can pass in, without having to parse the prop's options.
170+
171+
Another option would be to have the top-level key be the internal name:
172+
173+
```js
174+
props: {
175+
internalName: {
176+
as: 'externalName'
177+
}
178+
}
179+
```
180+
181+
This would make it easier when working _within_ the component to quickly see what props are available to be accessed.
182+
183+
The proposal currently puts more emphasis on the consumer's glanceability, since you usually have more knowledge of a component while you're working on it.
184+
185+
1. **What should be the name of the key in the prop's options used for aliasing the prop?**
186+
187+
This proposal uses `as`, but there are many different keys that could be considered (depending on whether it denotes an internal name or an external name).
188+
189+
Here are some alternate names:
190+
191+
- `alias`
192+
- `label`
193+
- `expose`
194+
- `internal`
195+
- `external`

0 commit comments

Comments
 (0)