Skip to content

Commit

Permalink
Update doc regarding class fields (#1244)
Browse files Browse the repository at this point in the history
  • Loading branch information
augustjk authored Nov 3, 2023
1 parent 381119c commit de86252
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ TypeScript supports both experimental decorators and standard decorators. We rec

To use experimental decorators you must enable the `experimentalDecorators` compiler option.

You should also ensure that the `useDefineForClassFields` setting is `false`. Note, this is only required when the `target` is set to `ES2022` or greater, but it is recommended to explicitly set this to `false`.
You should also ensure that the `useDefineForClassFields` setting is `false`. This is only required when `target` is set to `ES2022` or greater, but it is recommended to explicitly set this to `false`. This is needed to [avoid issues with class fields when declaring properties](/docs/v3/components/properties/#avoiding-issues-with-class-fields).

```json
// tsconfig.json
Expand Down Expand Up @@ -125,12 +125,6 @@ Enable decorators by adding [`@babel/plugin-proposal-decorators`](https://babelj

Note: Lit decorators only work with `"version": "2023-05"`. Other versions, including the formerly supported `"2018-09"`, are not supported.

### Avoiding issues with class fields and decorators {#avoiding-issues-with-class-fields}

Standard [class fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields) have a problematic interaction with declaring reactive properties. See [Avoiding issues with class fields when declaring properties](/docs/v3/components/properties/#avoiding-issues-with-class-fields) for more information.

When using decorators, transpiler settings for Babel and TypeScript must be configured correctly as shown in the sections above for [TypeScript](#decorators-typescript) and [Babel](#decorators-babel).

## Decorator versions

Decorators are a [stage 3 proposal](https://github.com/tc39/proposal-decorators) for addition to the ECMAScript standard. Compilers like [Babel](https://babeljs.io/) and [TypeScript](https://www.typescriptlang.org/) support decorators, though no browsers have implemented them yet. Lit decorators work with Babel and TypeScript, and will work in browsers when they implement them natively.
Expand Down
77 changes: 60 additions & 17 deletions packages/lit-dev-content/site/docs/v3/components/properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,34 +120,77 @@ An empty option object is equivalent to specifying the default value for all opt

### Avoiding issues with class fields when declaring properties {#avoiding-issues-with-class-fields}

[Class fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields) have a problematic interaction with reactive properties. Class fields are defined on the element instance. Reactive properties are defined as accessors on the element prototype. According to the rules of JavaScript, an instance property takes precedence over and effectively hides a prototype property. This means that reactive property accessors do not function when class fields are used. When a property is set, the element does not update.
[Class fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields) have a problematic interaction with reactive properties. Class fields are defined on the element instance whereas reactive properties are defined as accessors on the element prototype. According to the rules of JavaScript, an instance property takes precedence over and effectively hides a prototype property. This means that reactive property accessors do not function when class fields are used such that setting the property won't trigger an element update.

In **JavaScript** you **must not use class fields** when declaring reactive properties. Instead, properties must be initialized in the element constructor:
```js
class MyElement extends LitElement {
static properties = {foo: {type: String}}
foo = 'Default'; // ❌ this will make `foo` not reactive
}
```

In **JavaScript**, you **must not use class fields** when declaring reactive properties. Instead, properties must be initialized in the element constructor:
```js
constructor() {
super();
this.data = {};
class MyElement extends LitElement {
static properties = {foo: {type: String}}
constructor() {
super();
this.foo = 'Default';
}
}
```

For **TypeScript**, you **may use class fields** for declaring reactive properties as long as you use one of these patterns:
* Set the `useDefineForClassFields` setting in your `tsconfig` to `false`. Note, this is not required for some configurations of TypeScript, but it's recommended to explicitly set it to `false`.
* Add the `declare` keyword on the field, and put the field's initializer in the constructor.
Alternatively, you may use [standard decorators with Babel](/docs/v3/components/decorators/#decorators-babel) to declare reactive properties.
```ts
class MyElement extends LitElement {
@property()
accessor foo = 'Default';
}
```

When compiling JavaScript with **Babel**, you **may use class fields** for declaring reactive properties as long as you set `setPublicClassFields` to `true` in the `assumptions` config of your `babelrc`. Note, for older versions of Babel, you also need to include the plugin `@babel/plugin-proposal-class-properties`:
For **TypeScript**, you **may use class fields** for declaring reactive properties as long as you use one of these patterns:
* Set the `useDefineForClassFields` compiler option to `false`. This is already the recommendation when [using decorators with TypeScript](/docs/v3/components/decorators/#decorators-typescript).
```json
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true, // If using decorators
"useDefineForClassFields": false,
}
}
```
```ts
class MyElement extends LitElement {
static properties = {foo: {type: String}}
foo = 'Default';

```js
assumptions = {
"setPublicClassFields": true
};
@property()
bar = 'Default';
}
```

plugins = [
["@babel/plugin-proposal-class-properties"],
];
* Add the `declare` keyword on the field, and put the field's initializer in the constructor.
```ts
class MyElement extends LitElement {
declare foo: string;
static properties = {foo: {type: String}}
constructor() {
super();
this.foo = 'Default';
}
}
```

For information about using class fields with **decorators**, see [Avoiding issues with class fields and decorators](/docs/v3/components/decorators/#avoiding-issues-with-class-fields).
* Add the `accessor` keyword on the field to use [auto-accessors](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#auto-accessors-in-classes).
```ts
class MyElement extends LitElement {
static properties = {foo: {type: String}}
accessor foo = 'Default';

@property()
accessor bar = 'Default';
}
```

### Property options

Expand Down

0 comments on commit de86252

Please sign in to comment.