在视图模板中可以通过 slot 声明一个插槽的位置,其位置的内容可以由外层组件定义。

  1. var Panel = san.defineComponent({
  2. template: '<div>'
  3. + ' <div class="head" on-click="toggle">title</div>'
  4. + ' <p style="{{fold | yesToBe(\'display:none\')}}"><slot></slot></p>'
  5. + '</div>',
  6. toggle: function () {
  7. this.data.set('fold', !this.data.get('fold'));
  8. }
  9. });
  10. var MyComponent = san.defineComponent({
  11. components: {
  12. 'ui-panel': Panel
  13. },
  14. template: '<div><ui-panel>Hello San</ui-panel></div>'
  15. });
  16. /* MyComponent渲染结果
  17. <div>
  18. <div class="head">title</div>
  19. <p style="display:none">Hello San</p>
  20. </div>
  21. */

HTML 标准关于 slot 的描述可以参考 这里

数据环境

插入 slot 部分的内容,其数据环境为 声明时的环境

  1. var Panel = san.defineComponent({
  2. template: '<div>'
  3. + ' <div class="head" on-click="toggle">title</div>'
  4. + ' <p><slot></slot></p>'
  5. + '</div>',
  6. initData: function () {
  7. return {name: 'Panel'};
  8. }
  9. });
  10. var MyComponent = san.defineComponent({
  11. components: {
  12. 'ui-panel': Panel
  13. },
  14. template: '<div><ui-panel>I am {{name}}</ui-panel></div>',
  15. initData: function () {
  16. return {name: 'MyComponent'};
  17. }
  18. });
  19. /* MyComponent渲染结果
  20. <div>
  21. <div class="head">title</div>
  22. <p>I am MyComponent</p>
  23. </div>
  24. */

命名

通过 name 属性可以给 slot 命名。一个视图模板的声明可以包含一个默认 slot 和多个命名 slot。外层组件的元素通过 slot="name" 的属性声明,可以指定自身的插入点。

  1. var Tab = san.defineComponent({
  2. template: '<div>'
  3. + ' <header><slot name="title"></slot></header>'
  4. + ' <main><slot></slot></main>'
  5. + '</div>'
  6. });
  7. var MyComponent = san.defineComponent({
  8. components: {
  9. 'ui-tab': Tab
  10. },
  11. template: '<div><ui-tab>'
  12. + '<h3 slot="title">1</h3><p>one</p>'
  13. + '<h3 slot="title">2</h3><p>two</p>'
  14. + '</ui-tab></div>'
  15. });
  16. /* MyComponent渲染结果
  17. <div>
  18. <header><h3>1</h3><h3>2</h3></header>
  19. <main><p>one</p><p>two</p></main>
  20. </div>
  21. */

注意:外层组件的替换元素,只有在直接子元素上才能声明 slot="name" 指定自身的插入点。下面例子中的 a 元素无法被插入 title slot。

  1. var Tab = san.defineComponent({
  2. template: '<div>'
  3. + ' <header><slot name="title"></slot></header>'
  4. + ' <main><slot></slot></main>'
  5. + '</div>'
  6. });
  7. var MyComponent = san.defineComponent({
  8. components: {
  9. 'ui-tab': Tab
  10. },
  11. template: '<div><ui-tab>'
  12. + '<h3 slot="title">1</h3><p>one</p>'
  13. + '<h3 slot="title">2</h3><p>two<a slot="title">slot fail</a></p>'
  14. + '</ui-tab></div>'
  15. });
  16. /* MyComponent渲染结果,a 元素无法被插入 title slot
  17. <div>
  18. <header><h3>1</h3><h3>2</h3></header>
  19. <main><p>one</p><p>two<a>slot fail</a></p></main>
  20. </div>
  21. */

插槽指令应用

版本:>= 3.3.0

在 slot 声明时应用 if 或 for 指令,可以让插槽根据组件数据动态化。

if指令

下面的例子中,panel 的 hidden 属性为 true 时,panel 中默认插槽将不会渲染,仅包含 title 插槽,通过 slot 方法获取的数组长度为 0。

  1. var Panel = san.defineComponent({
  2. template: '<div><slot name="title"/><slot s-if="!hidden"/></div>',
  3. });
  4. var MyComponent = san.defineComponent({
  5. components: {
  6. 'x-panel': Panel
  7. },
  8. template: ''
  9. + '<div>'
  10. + '<x-panel hidden="{{folderHidden}}" s-ref="panel">'
  11. + '<b slot="title">{{name}}</b>'
  12. + '<p>{{desc}}</p>'
  13. + '</x-panel>'
  14. + '</div>',
  15. attached: function () {
  16. // 0
  17. this.ref('panel').slot().length
  18. }
  19. });
  20. var myComponent = new MyComponent({
  21. data: {
  22. folderHidden: true,
  23. desc: 'MVVM component framework',
  24. name: 'San'
  25. }
  26. });
  27. /* MyComponent渲染结果,hidden为true所以不包含default slot
  28. <div>
  29. <b>San</b>
  30. </div>
  31. */

for指令

下面的例子没什么用,纯粹为了演示 slot 上应用 for 指令。在后续 scoped 插槽 章节中可以看到有意义的场景。

  1. var Panel = san.defineComponent({
  2. template: '<div><slot s-for="item in data"/></div>',
  3. });
  4. var MyComponent = san.defineComponent({
  5. components: {
  6. 'x-panel': Panel
  7. },
  8. template: ''
  9. + '<div>'
  10. + '<x-panel data="{{panelData}}" s-ref="panel">'
  11. + '<p>{{name}}</p>'
  12. + '</x-panel>'
  13. + '</div>',
  14. attached: function () {
  15. // 0
  16. this.ref('panel').slot().length
  17. }
  18. });
  19. var myComponent = new MyComponent({
  20. data: {
  21. panelData: [1, 2, 3],
  22. name: 'San'
  23. }
  24. });
  25. /* MyComponent渲染结果,<p>{{name}}</p>输出 3 遍
  26. <div>
  27. <p>San</p>
  28. <p>San</p>
  29. <p>San</p>
  30. </div>
  31. */

scoped 插槽

如果 slot 声明中包含 s-bind 或 1 个以上 var- 数据前缀声明,该 slot 为 scoped slot。scoped slot 具有独立的 数据环境

scoped slot 通常用于组件的视图部分期望由 外部传入视图结构,渲染过程使用组件内部数据。

注意:scoped slot 中不支持双向绑定。

var

版本:>= 3.3.0

var- 的 scoped 数据声明的形式为 var-name=”expression”

  1. var Men = san.defineComponent({
  2. template: '<div>'
  3. + '<slot s-for="item in data" var-n="item.name" var-email="item.email" var-sex="item.sex ? \'male\' : \'female\'">'
  4. + '<p>{{n}},{{sex}},{{email}}</p>'
  5. + '</slot>'
  6. + '</div>'
  7. });
  8. var MyComponent = san.defineComponent({
  9. components: {
  10. 'x-men': Men
  11. },
  12. template: '<div><x-men data="{{men}}" s-ref="men">'
  13. + '<h3>{{n}}</h3>'
  14. + '<p><b>{{sex}}</b><u>{{email}}</u></p>'
  15. + '</x-men></div>',
  16. attached: function () {
  17. var slots = this.ref('men').slot();
  18. // 3
  19. slots.length
  20. // truthy
  21. slots[0].isInserted
  22. // truthy
  23. contentSlot.isScoped
  24. }
  25. });
  26. var myComponent = new MyComponent({
  27. data: {
  28. men: [
  29. {name: 'errorrik', sex: 1, email: 'errorrik@gmail.com'},
  30. {name: 'leeight', sex: 0, email: 'leeight@gmail.com'},
  31. {name: 'otakustay', email: 'otakustay@gmail.com', sex: 1}
  32. ]
  33. }
  34. });
  35. /* MyComponent渲染结果
  36. <div>
  37. <h3>errorrik</h3>
  38. <p><b>male</b><u>errorrik@gmail.com</u></p>
  39. <h3>leeight</h3>
  40. <p><b>female</b><u>leeight@gmail.com</u></p>
  41. <h3>otakustay</h3>
  42. <p><b>male</b><u>otakustay@gmail.com</u></p>
  43. </div>
  44. */

s-bind

版本:>= 3.6.0

s-bind 的 scoped 数据声明的形式为 s-bind=”expression”

s-bindvar- 并存时,var- 将覆盖整体绑定中相应的数据项。

  1. var Men = san.defineComponent({
  2. template: '<div>'
  3. + '<slot s-for="item in data" s-bind="{n: item.name, email: item.email, sex: item.sex ? \'male\' : \'female\'}">'
  4. + '<p>{{n}},{{sex}},{{email}}</p>'
  5. + '</slot>'
  6. + '</div>'
  7. });
  8. var MyComponent = san.defineComponent({
  9. components: {
  10. 'x-men': Men
  11. },
  12. template: '<div><x-men data="{{men}}" s-ref="men">'
  13. + '<h3>{{n}}</h3>'
  14. + '<p><b>{{sex}}</b><u>{{email}}</u></p>'
  15. + '</x-men></div>',
  16. attached: function () {
  17. var slots = this.ref('men').slot();
  18. // 3
  19. slots.length
  20. // truthy
  21. slots[0].isInserted
  22. // truthy
  23. contentSlot.isScoped
  24. }
  25. });
  26. var myComponent = new MyComponent({
  27. data: {
  28. men: [
  29. {name: 'errorrik', sex: 1, email: 'errorrik@gmail.com'},
  30. {name: 'leeight', sex: 0, email: 'leeight@gmail.com'},
  31. {name: 'otakustay', email: 'otakustay@gmail.com', sex: 1}
  32. ]
  33. }
  34. });
  35. /* MyComponent渲染结果
  36. <div>
  37. <h3>errorrik</h3>
  38. <p><b>male</b><u>errorrik@gmail.com</u></p>
  39. <h3>leeight</h3>
  40. <p><b>female</b><u>leeight@gmail.com</u></p>
  41. <h3>otakustay</h3>
  42. <p><b>male</b><u>otakustay@gmail.com</u></p>
  43. </div>
  44. */

访问环境数据

版本:>= 3.3.1

scoped slot 中,除了可以访问 var- 声明的数据外,还可以访问当前环境的数据。

  • 使用 slot 默认内容时,可以访问组件内部环境数据
  • 外层组件定义的 slot 内容,可以访问外层组件环境的数据
  1. var Man = san.defineComponent({
  2. template: '<p>'
  3. + '<slot var-n="who.name" var-email="who.email">'
  4. + '{{n}},{{email}},{{country}}'
  5. + '</slot>'
  6. + '</p>'
  7. });
  8. var MyComponent = san.defineComponent({
  9. components: {
  10. 'x-man': Man
  11. },
  12. template: ''
  13. + '<div><x-man who="{{man}}" country="{{country}}">'
  14. + '<b>{{n}} - {{province}}</b>'
  15. + '<u>{{email}}</u>'
  16. + '</x-men></div>'
  17. });
  18. var myComponent = new MyComponent({
  19. data: {
  20. man: {
  21. name: 'errorrik',
  22. email: 'errorrik@gmail.com'
  23. },
  24. country: 'China',
  25. province: 'HN'
  26. }
  27. });
  28. /* MyComponent渲染结果
  29. <div>
  30. <p>
  31. <b>errorrik - HN</b>
  32. <u>errorrik@gmail.com</u>
  33. </p>
  34. </div>
  35. */

动态命名

版本:>= 3.3.1

slot 声明中,组件可以使用当前的数据环境进行命名,从而提供动态的插槽。插槽的动态命名常用于 组件结构根据数据生成 的场景下,比如表格组件。

  1. var Table = san.defineComponent({
  2. template: ''
  3. + '<table>'
  4. + '<thead><tr><th s-for="col in columns">{{col.label}}</th></tr></thead>'
  5. + '<tbody>'
  6. + '<tr s-for="row in datasource">'
  7. + '<td s-for="col in columns">'
  8. + '<slot name="col-{{col.name}}" var-row="row" var-col="col">{{row[col.name]}}</slot>'
  9. + '</td>'
  10. + ' </tr>'
  11. + '</tbody>'
  12. + '</table>'
  13. });
  14. var MyComponent = san.defineComponent({
  15. components: {
  16. 'x-table': Table
  17. },
  18. template: ''
  19. + '<div>'
  20. + '<x-table columns="{{columns}}" datasource="{{list}}">'
  21. + '<b slot="col-name">{{row.name}}</b>'
  22. + '</x-table>'
  23. + '</div>'
  24. });
  25. var myComponent = new MyComponent({
  26. data: {
  27. columns: [
  28. {name: 'name', label: '名'},
  29. {name: 'email', label: '邮'}
  30. ],
  31. list: [
  32. {name: 'errorrik', email: 'errorrik@gmail.com'},
  33. {name: 'leeight', email: 'leeight@gmail.com'}
  34. ]
  35. }
  36. });
  37. /* MyComponent渲染结果
  38. <div>
  39. <table>
  40. <thead>
  41. <tr>
  42. <th>名</th>
  43. <th>邮</th>
  44. </tr>
  45. </thead>
  46. <tbody>
  47. <tr>
  48. <td><b>errorrik</b></td>
  49. <td>errorrik@gmail.com</td>
  50. </tr>
  51. <tr>
  52. <td><b>leeight</b></td>
  53. <td>leeight@gmail.com</td>
  54. </tr>
  55. </tbody>
  56. </table>
  57. </div>
  58. */

注意:表格的视图更新在 IE 下可能存在兼容性问题。