Repository: vuejs/vue
Pull request: Typings: Improve $slots and $scopedSlots type to prevent unchecked access to undefined

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**

When working directly with render functions in TypeScript / tsx, currently the following code is unsafe and unchecked by the ts compiler:

```ts
this.$sopedSlots.default() // default can be undefined
```

The change in this PR makes TypeScript complain about the unsafe access accordingly and the code has to be expressed in a way that prevents calling an undefined function, e.g.:

```ts
( this.$scopedSlots.default || ( () => 'default value' ) )( props )
```

Changed files:
filename: types/test/options-test.ts
patch: @@ -304,8 +304,8 @@ Vue.component('component-with-scoped-slot', {
     child: {
       render (this: Vue, h: CreateElement) {
         return h('div', [
-          this.$scopedSlots['default']({ msg: 'hi' }),
-          this.$scopedSlots['item']({ msg: 'hello' })
+          this.$scopedSlots['default']!({ msg: 'hi' }),
+          this.$scopedSlots['item']!({ msg: 'hello' })
         ])
       }
     }
@@ -314,7 +314,7 @@ Vue.component('component-with-scoped-slot', {
 
 Vue.component('narrow-array-of-vnode-type', {
   render (h): VNode {
-    const slot = this.$scopedSlots.default({})
+    const slot = this.$scopedSlots.default!({})
     if (typeof slot !== 'string') {
       const first = slot[0]
       if (!Array.isArray(first) && typeof first !== 'string') {

filename: types/vue.d.ts
patch: @@ -27,8 +27,8 @@ export interface Vue {
   readonly $root: Vue;
   readonly $children: Vue[];
   readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] };
-  readonly $slots: { [key: string]: VNode[] };
-  readonly $scopedSlots: { [key: string]: ScopedSlot };
+  readonly $slots: { [key: string]: VNode[] | undefined };
+  readonly $scopedSlots: { [key: string]: ScopedSlot | undefined };
   readonly $isServer: boolean;
   readonly $data: Record<string, any>;
   readonly $props: Record<string, any>;