From de8625209a92f7762e969bd4b6098f22a7311f68 Mon Sep 17 00:00:00 2001 From: Augustine Kim Date: Fri, 3 Nov 2023 10:05:26 -0700 Subject: [PATCH] Update doc regarding class fields (#1244) --- .../site/docs/v3/components/decorators.md | 8 +- .../site/docs/v3/components/properties.md | 77 +++++++++++++++---- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/packages/lit-dev-content/site/docs/v3/components/decorators.md b/packages/lit-dev-content/site/docs/v3/components/decorators.md index 0d9ea5c74..430a1886f 100644 --- a/packages/lit-dev-content/site/docs/v3/components/decorators.md +++ b/packages/lit-dev-content/site/docs/v3/components/decorators.md @@ -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 @@ -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. diff --git a/packages/lit-dev-content/site/docs/v3/components/properties.md b/packages/lit-dev-content/site/docs/v3/components/properties.md index 4250a5365..09be0f517 100644 --- a/packages/lit-dev-content/site/docs/v3/components/properties.md +++ b/packages/lit-dev-content/site/docs/v3/components/properties.md @@ -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