|
| 1 | +--- |
| 2 | +title: Usage |
| 3 | +description: How to declare and use JS interop members. |
| 4 | +--- |
| 5 | + |
| 6 | +JS interop works by looking up properties in the [global JS scope]. In order to |
| 7 | +interact with these properties, you use [`external`] interop members. In order |
| 8 | +to provide types for these properties, you use interop types. |
| 9 | + |
| 10 | +## Interop types |
| 11 | + |
| 12 | +When interacting with a JS value, you need to provide a Dart type for it. In |
| 13 | +order to do this, you either use or declare an interop type. Dart provides a set |
| 14 | +of core interop types called ["JS types"] that you can use to add types to your |
| 15 | +interop members. You can also declare an interop type using [extension types] by |
| 16 | +making its representation type another interop type. "JS types" refer to the |
| 17 | +interop types provided by Dart, and while all JS types are interop types, not |
| 18 | +all interop types are JS types. For example: |
| 19 | + |
| 20 | +```dart |
| 21 | +extension type Console(JSObject _) implements JSObject {} |
| 22 | +``` |
| 23 | + |
| 24 | +Both `Console` and `JSObject` are interop types, but only `JSObject` is a JS |
| 25 | +type. |
| 26 | + |
| 27 | +You can also use `Console` as a representation type of an interop type as it is |
| 28 | +an interop type: |
| 29 | + |
| 30 | +```dart |
| 31 | +extension type Console2(Console _) implements Console {} |
| 32 | +``` |
| 33 | + |
| 34 | +Because you're using extension types, both `Console` and `Console2` are still |
| 35 | +`JSObject`s. You're just providing a Dart interface to view the `JSObject` |
| 36 | +differently. There is no guarantee that they are a specific type of JS object. |
| 37 | + |
| 38 | +Generally, you will likely use `JSObject` as the representation type for interop |
| 39 | +types you declare because you're likely interacting with JS objects who don't |
| 40 | +have a corresponding JS type like [`Window`]. |
| 41 | + |
| 42 | +Interop types should also generally implement their representation type so that |
| 43 | +they can be subtypes of the representation type. |
| 44 | + |
| 45 | +## Interop members |
| 46 | + |
| 47 | +In order to get a JS value that you can provide a type for, you'll want to use |
| 48 | +an `external` interop member. These members might take in arguments and return a |
| 49 | +type. The types that can be used on these members have [restrictions]. There are |
| 50 | +various ways you can declare interop members. |
| 51 | + |
| 52 | +### Top-level interop members |
| 53 | + |
| 54 | +```dart |
| 55 | +@JS() |
| 56 | +external Console get console; |
| 57 | +
|
| 58 | +@JS() |
| 59 | +external set name(String value); |
| 60 | +
|
| 61 | +@JS() |
| 62 | +external void close(); |
| 63 | +``` |
| 64 | + |
| 65 | +Here, `console`, `name`, and `close` are top-level interop members. When |
| 66 | +`console` is called, the value in the `console` property in the global namespace |
| 67 | +is retrieved and casted to `Console`. When `name` is set, the property `name` in |
| 68 | +the global namespace is set to the given value. When `close` is called, the |
| 69 | +function `close` in the global namespace is called with no arguments. You can |
| 70 | +declare top-level interop getters, setters, methods, and fields, which are just |
| 71 | +pairs of getters and setters. |
| 72 | + |
| 73 | +### Interop type members |
| 74 | + |
| 75 | +```dart |
| 76 | +extension type Array._(JSObject _) implements JSObject { |
| 77 | + external Array(); |
| 78 | + external factory Array.withLength(int length); |
| 79 | +
|
| 80 | + external static bool isArray(JSObject o); |
| 81 | +
|
| 82 | + external int length; |
| 83 | + external JSAny? at(int index); |
| 84 | +
|
| 85 | + external operator [](int index); |
| 86 | + external operator []=(int index, JSAny? any); |
| 87 | +
|
| 88 | + bool get isZeroLength => length == 0; |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +Here we are using `Array` to interop with JS' [`Array`] type. |
| 93 | + |
| 94 | +Within an interop type, you can declare several different types of `external` |
| 95 | +interop members: |
| 96 | + |
| 97 | +- Constructors. When called, these members construct a new JS object whose |
| 98 | + constructor is defined by the name of the extension type using `new`. For |
| 99 | + example, calling `Array()` in Dart will generate a JS invocation that looks |
| 100 | + like `new Array()`. Similarly, calling `Array.withLength(10)` will generate a |
| 101 | + JS invocation that looks like `new Array(10)`. Note that the JS invocations of |
| 102 | + all `external` interop constructors follow the same semantics, regardless of |
| 103 | + whether they're given a name in Dart or if they are a factory. |
| 104 | + |
| 105 | +- `static` members. Like constructors, these members use the name of the |
| 106 | + extension type to generate the JS code. For example, calling `Array.isArray()` |
| 107 | + will generate a JS invocation that looks like `Array.isArray()`. Like |
| 108 | + top-levels, you can declare `static` methods, getters, setters, and fields. |
| 109 | + |
| 110 | +- Instance members. Like with other Dart types, these members require an |
| 111 | + instance in order to be used. These members get, set, or invoke properties on |
| 112 | + the instance. For example: |
| 113 | + |
| 114 | + ```dart |
| 115 | + final array = Array(); |
| 116 | + array.length = 10; |
| 117 | + final length = array.length; |
| 118 | + for (final i = 0; i <= 10; i++) { |
| 119 | + array[i] = i.toJS; |
| 120 | + assert(array[i] == array.at(i)); |
| 121 | + } |
| 122 | + ``` |
| 123 | + |
| 124 | + The call to `array.length` gets the value of the `length` property of `array`. |
| 125 | + The call to `array.length = 10` sets the value of the `length` property. The |
| 126 | + call to `array.at(i)` calls the function in the `at` property of `array` with |
| 127 | + the given arguments and returns the value of that call. Like top-levels and |
| 128 | + `static` members, you can declare instance methods, getters, setters, and |
| 129 | + fields. |
| 130 | + |
| 131 | +- Operators. There are only two `external` interop operators allowed in interop |
| 132 | + types: `[]` and `[]=`. These are instance members that match the semantics of |
| 133 | + JS' [property accessors]. In the above example, `array[i]` gets the value in |
| 134 | + the `i`th slot of `array`, and `array[i]` sets the value in that slot to |
| 135 | + `i.toJS`. We expose other JS operators through [utility functions] in |
| 136 | + `dart:js_interop`. |
| 137 | + |
| 138 | +In the [JS types] section, you'll see that there exists a predefined JS type for |
| 139 | +`Array` called `JSArray`. Here, we're writing another interop type for the same |
| 140 | +JS type and just *viewing* it differently, so there is no conflict. If we |
| 141 | +wanted, we could have made the representation type `JSArray`: |
| 142 | + |
| 143 | +```dart |
| 144 | +extension type Array._(JSArray<JSAny?> _) implements JSArray<JSAny?> {} |
| 145 | +``` |
| 146 | + |
| 147 | +Lastly, like any other extension type, you're allowed to declare any |
| 148 | +non-`external` members in the interop type. `isZeroLength` is one such example. |
| 149 | + |
| 150 | +#### Object literal constructors |
| 151 | + |
| 152 | +It is useful sometimes to create a JS [object literal] that simply contains a |
| 153 | +a number of properties and their values. In order to do this, we allow you to |
| 154 | +use a constructor with only named arguments: |
| 155 | + |
| 156 | +```dart |
| 157 | +import ‘dart:js_interop’; |
| 158 | +
|
| 159 | +extension type Options._(JSObject o) { |
| 160 | + external Options({int a, int b}); |
| 161 | + external int get a; |
| 162 | + external int get b; |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +A call to `Options(a: 0, b: 1)` will result in a JS invocation of |
| 167 | +`{a: 0, b: 1}`. You can get or set these values through `external` instance |
| 168 | +members. |
| 169 | + |
| 170 | +### Extension members on interop types |
| 171 | + |
| 172 | +You can also write `external` members in extensions of interop types. For |
| 173 | +example: |
| 174 | + |
| 175 | +```dart |
| 176 | +extension on Array { |
| 177 | + external int push(JSAny? any); |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +The semantics of calling `push` are identical to what it would have been if it |
| 182 | +was in the definition of `Array` instead. The only `external` members you can |
| 183 | +write in such an extension are instance members and the allowed operators. Like |
| 184 | +with interop types, you can write any non-`external` members in the extension. |
| 185 | +These extensions are useful for when an interop type doesn't expose the |
| 186 | +`external` member you need and you don't want to create a new interop type. |
| 187 | + |
| 188 | +### Parameters |
| 189 | + |
| 190 | +`external` interop methods can only contain positional and optional arguments. |
| 191 | +The one exception is object literal constructors, where they can contain *only* |
| 192 | +named arguments. |
| 193 | + |
| 194 | +Unlike with non-`external` methods, optional arguments do not get replaced with |
| 195 | +their default value, but are instead omitted. For example: |
| 196 | + |
| 197 | +```dart |
| 198 | +external int push(JSAny? any, [JSAny? any2]); |
| 199 | +``` |
| 200 | + |
| 201 | +Calling `array.push(0.toJS)` in Dart will result in a JS invocation of |
| 202 | +`array.push(0.toJS)` and *not* `array.push(0.toJS, null)`. If you declare a |
| 203 | +parameter with an explicit default value, you will get a warning that the value |
| 204 | +will be ignored. |
| 205 | + |
| 206 | +## `@JS()` |
| 207 | + |
| 208 | +It is sometimes useful to refer to a JS property with a different name than the |
| 209 | +one written. For example, if you want to write two `external` APIs that point to |
| 210 | +the same JS property, you’d need to write a different name for at least one of |
| 211 | +them. In order to do this, you can use the [`@JS()`] annotation with a constant |
| 212 | +string value. For example: |
| 213 | + |
| 214 | +```dart |
| 215 | +extension type Array._(JSArray<JSAny?> _) implements JSArray<JSAny?> { |
| 216 | + external int push(JSNumber number); |
| 217 | + @JS('push') |
| 218 | + external int pushString(JSString string); |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +Calling either `push` or `pushString` will result in JS code that uses `push`. |
| 223 | + |
| 224 | +You can also rename interop types: |
| 225 | + |
| 226 | +```dart |
| 227 | +@JS('Date') |
| 228 | +extension type JSDate._(JSObject _) implements JSObject { |
| 229 | + external JSDate(); |
| 230 | +
|
| 231 | + external static int now(); |
| 232 | +} |
| 233 | +``` |
| 234 | + |
| 235 | +Calling `JSDate()` will result in a JS invocation of `new Date()`. Similarly, |
| 236 | +calling `JSDate.now()` will result in a JS invocation of `Date.now()`. |
| 237 | + |
| 238 | +Furthermore, you can namespace an entire library, which will add a prefix to all |
| 239 | +interop top-level members, interop types, and `static` interop members within |
| 240 | +those types: |
| 241 | + |
| 242 | +```dart |
| 243 | +@JS('library1') |
| 244 | +library; |
| 245 | +
|
| 246 | +import 'dart:js_interop'; |
| 247 | +
|
| 248 | +@JS() |
| 249 | +external void method(); |
| 250 | +
|
| 251 | +extension type JSType(JSObject _) implements JSObject { |
| 252 | + external JSType(); |
| 253 | +
|
| 254 | + external static int get staticMember; |
| 255 | +} |
| 256 | +``` |
| 257 | + |
| 258 | +Calling `method()` will result in a JS invocation of `library1.method()`, |
| 259 | +calling `JSType()` will result in a JS invocation of `new library1.JSType()`, |
| 260 | +and calling `JSType.staticMember` will result in a JS invocation of |
| 261 | +`library1.JSType.staticMember`. |
| 262 | + |
| 263 | +Unlike interop members and interop types, we only ever add a library name in the |
| 264 | +JS invocation if you provide a non-empty value in the `@JS()` annotation on the |
| 265 | +library. |
| 266 | + |
| 267 | +```dart |
| 268 | +library interop_library; |
| 269 | +
|
| 270 | +import 'dart:js_interop'; |
| 271 | +
|
| 272 | +@JS() |
| 273 | +external void method(); |
| 274 | +``` |
| 275 | + |
| 276 | +Calling `method()` will result in a JS invocation of `method()` and *not* |
| 277 | +`interop_library.method()`. |
| 278 | + |
| 279 | +You can also write multiple namespaces delimited by a '.' for libraries, |
| 280 | +top-level members, and interop types: |
| 281 | + |
| 282 | +```dart |
| 283 | +@JS('library1.library2') |
| 284 | +library; |
| 285 | +
|
| 286 | +import 'dart:js_interop'; |
| 287 | +
|
| 288 | +@JS('library3.method') |
| 289 | +external void method(); |
| 290 | +
|
| 291 | +@JS('library3.JSType') |
| 292 | +extension type JSType(JSObject _) implements JSObject { |
| 293 | + external JSType(); |
| 294 | +} |
| 295 | +``` |
| 296 | + |
| 297 | +Calling `method()` will result in a JS invocation of |
| 298 | +`library1.library2.library3.method()`, calling `JSType()` will result in a JS |
| 299 | +invocation of `new library1.library2.library3.JSType()`, and so forth. |
| 300 | + |
| 301 | +You can't use `@JS()` annotations with '.' in the value on interop type members |
| 302 | +or extension members of interop types, however. |
| 303 | + |
| 304 | +If there is no value provided to `@JS()` or the value is empty, no renaming will |
| 305 | +occur. |
| 306 | + |
| 307 | +`@JS()` also tells the compiler that a member or type is intended to be treated |
| 308 | +as a JS interop member or type. It is required (with or without a value) for all |
| 309 | +top-level members to distinguish them from other `external` top-level members, |
| 310 | +but can often be elided on and within interop types and on extension members as |
| 311 | +the compiler can tell from the representation type and on-type. |
| 312 | + |
| 313 | +## `dart:js_interop` and `dart:js_interop_unsafe` |
| 314 | + |
| 315 | +[`dart:js_interop`] contains all the necessary members you should need, |
| 316 | +including `@JS`, JS types, conversion functions, and various utility functions. |
| 317 | +Utility functions include: |
| 318 | + |
| 319 | +- [`globalContext`], which represents the global namespace that the compilers |
| 320 | + use to generate JS invocations. |
| 321 | +- [Helpers to type-check JS values] |
| 322 | +- JS operators |
| 323 | +- [`dartify`] and [`jsify`], which type-check and auto-convert certain JS values |
| 324 | + to Dart values and vice versa. |
| 325 | + |
| 326 | +and more. More utilities might be added to this library in the future. |
| 327 | + |
| 328 | +[`dart:js_interop_unsafe`] contains members that allow you to look up properties |
| 329 | +dynamically. For example: |
| 330 | + |
| 331 | +```dart |
| 332 | +JSFunction f = console['log']; |
| 333 | +``` |
| 334 | + |
| 335 | +Instead of declaring an interop member named `log`, we're instead using a string |
| 336 | +to represent the property. `dart:js_interop_unsafe` provides functionality to |
| 337 | +dynamically get, set, and call properties. |
| 338 | + |
| 339 | +{{site.alert.warn}} |
| 340 | +Avoid using `dart:js_interop_unsafe` if possible. It makes security compliance |
| 341 | +more difficult to guarantee and may lead to violations. |
| 342 | +{{site.alert.end}} |
| 343 | + |
| 344 | +{% comment %} |
| 345 | +TODO: add links (with stable) when ready: |
| 346 | +TODO: How do you link to a subsection of another section? |
| 347 | +{% endcomment %} |
| 348 | + |
| 349 | +[global JS scope]: https://developer.mozilla.org/en-US/docs/Glossary/Global_scope |
| 350 | +[`external`]: https://dart.dev/language/keywords |
| 351 | +["JS types"]: /interop/js-interop/js-types |
| 352 | +[extension types]: / |
| 353 | +[`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window |
| 354 | +[restrictions]: / |
| 355 | +[`Date`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date |
| 356 | +[property accessors]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation |
| 357 | +[utility functions]: https://api.dart.dev/dev/dart-js_interop/JSAnyOperatorExtension.html |
| 358 | +[JS types]: /interop/js-interop/js-types |
| 359 | +[`JSArray`]: https://api.dart.dev/dev/dart-js_interop/JSArray-extension-type.html |
| 360 | +[object literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer |
| 361 | +[`@JS()`]: https://api.dart.dev/dev/dart-js_interop/JS-class.html |
| 362 | +[`dart:js_interop`]: https://api.dart.dev/dev/dart-js_interop |
| 363 | +[`globalContext`]: https://api.dart.dev/dev/dart-js_interop/globalContext.html |
| 364 | +[Helpers to type-check JS values]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension.html |
| 365 | +[`dartify`]: https://api.dart.dev/dev/dart-js_interop/JSAnyUtilityExtension/dartify.html |
| 366 | +[`jsify`]: https://api.dart.dev/dev/dart-js_interop/NullableObjectUtilExtension.html |
| 367 | +[`dart:js_interop_unsafe`]: https://api.dart.dev/dev/dart-js_interop_unsafe |
| 368 | + |
0 commit comments