vue中,父組件可以向子組件傳遞一個組件(不僅是數據)嗎?

有下圖這樣一個列表,body中每個單元格里的內容可能是一個span(單行數據),可能是一個ul&>li(對象或多行數據,比如班級),甚至最後一個操作列裡面可能是select&>option,總之是未知的一個組件或html字元串。

那麼我在寫vue單元格組件的時候,希望外界能給它傳遞一些組件供其渲染數據。而單元格事先並不知道我要給它傳什麼組件(保持組件乾淨,它只按指定欄位接收組件),這種情況下,這個單元格組件怎麼寫呢?

具體一點就是:**怎麼讓一個組件接收 未知組件或html字元串 並渲染**(重點在於如何渲染,現在TableViewCell的prop中已經可以接收到了DropDown組件對象) 呢?

目前項目中組件的層級關係是:

(特定頁面)TaskPage -&> 傳遞數據 及 特定數據下在td標籤中的渲染組件,比如傳遞一個DropDown組件

|- (公共組件)TableView

|- (公共組件)TableViewCell(其實就是一個td標籤,把傳過來的DropDown組件渲染進td)

註:目前已知的一個解決方案是預先定好td的類型,傳入指定好的組件,但這樣並不通用,也很難保持組件的乾淨(我在sf提的問題:vue中,父組件可以向子組件傳遞一個組件(不僅是數據)嗎?感謝JasonKidd的回答,還有vue-beauty https://github.com/FE-Driver/vue-beauty開源項目的夥伴們),另一個方案是這兩天想出來的:使用Vue.partial,似乎是能把組件傳入了,但是有兩個問題:

  1. (? 已解決)模板中的變數還沒能綁定成功

  2. patial需要預先在「特定頁面」這個組件層使用語法進行全局註冊,還是不那麼優雅,可能會引發重複註冊的問題,因為這裡的聲明其他組件並不知道(難道還要再單獨export一個嗎?)

  3. 使用partial傳入的組件在devtool中不能顯示為組件,只相當於把模板傳入了,綁定了變數而已

據說 vue 2.0中就要取消這種用法了,引入了虛擬dom,可以像react一樣了。。。

所以目前還是沒有找到一個很好的辦法在vue 1中解決這個問題,不知還有什麼特性我沒有考慮到,所以繞了這樣一個彎路。是否有什麼優雅的實踐?


最近在做類似的事情,在okoala/vue-antd上學到一個做法。

首先,父組件傳入一個`render`函數,用於描述要動態生成的子組件。相關代碼如下:

const columns = [{
title: "操作",
key: "operation",
render: (text, record, index) =&> `&&`
}]

然後,對應的`table`組件中,對相應的`render`使用`$compile`進行渲染,並插入到相應的位置。這裡需要注意的是編譯作用域,需要使用父組件來編譯,否則無法綁定父組件的事件。相關代碼如下:

if (render) {
const $td = $tr.children[j]
const value = this.dataSource[i]

const template = render(value[dataIndex], value, i)
const $cell = document.createElement("div")

$cell.innerHTML = template
this.$parent.$compile($cell)

$td.appendChild($cell)
}


可以用數據+動態組件,我們用這樣的方式搞了一個拖拽組件自動化發布專題的系統。

&&

針對你的需求你也可以直接去看一些 Vue的組件庫,比如餓了么的 Mint UI 或者 Ant.D 的vue實現 GitHub - okoala/vue-antd: Vue UI Component Ant.Design


直接使用slot就行了,沒有那麼麻煩


這個很簡單,前些日子剛實現了一個這樣的需求,具體實現方法可以參考v-if這些自定義指令。寫一個自定義指令去生成渲染組件即可。

下面有些邏輯是因為是直接通過配置文件去生成頁面的所以需要你自己去除相關邏輯

你看主邏輯就好了,有時間我給你稍微講解下

let FragmentFactory = Vue.FragmentFactory;
let createAnchor = Vue.util.createAnchor;
let replace = Vue.util.replace;

Vue.directive("widget", {
terminal: true,
bind () {
let el = this.el;
let widget = this._scope.widget.component;
let widgetName = widget.name;
Vue.component(widgetName, widget);
let html = `&<${widgetName}&>&`;
el.innerHTML = html;
this.anchor = createAnchor("v-widget");//創建一些注釋做錨點插入片段用
replace(el, this.anchor);
let factory = new FragmentFactory(this.vm, el);// 下面這幾句是重點
this.frag = factory.create(this._host, this._scope, this._frag);
this.frag.before(this.anchor);
},
unbind () {
if (this.frag) {
this.frag.destroy();
}
}
});

調用這麼調用

div(style="display:inline" v-widget="widget")


個人理解,如果你要實現的是一個複合表格,其中的列包含多種交互形式,那麼首先這些組件是必須首先被實現的,因為業務需求是已知並可枚舉的,再次需要下來的數據中包含列模型來描述表格,最後依賴於Vue的動態組件就可以實現


不是十分理解你的問題。

或許可以試試使用slot分發內容。


你指的是slot嗎?

Edit fiddle - JSFiddle

// html
&
& &&</title&><br /> &<id slot="id"&>&</id&><br /> &</table-view&> &</div&> // js</p> <p>Vue.component("title",{<br /> template:`&<p&>我是title component&</p&>`<br /> })<br /> Vue.component("id",{<br /> template:`&<p&>我是id component&</p&>`<br /> })<br /> Vue.component("table-view",{<br /> template:`<br /> &<table&> &<tr&> &<td&>&<slot name="title"&>未傳入title&</slot&>&<td&> &<td&>&<slot name="id"&>未傳入ID&</slot&>&<td&> &</tr&> &</table&> `<br /> })<br /> new Vue({<br /> el:"#root"<br /> })<br /> </code></pre> <p></P></p> <p><center> <script src="/336-5.js"></script></center></p> <hr /><span style="color:red"><i class="fa fa-paper-plane"></i></span> 推薦閱讀:</div> <p>※<a target=_blank href=/p20180111367468470/>關於Vue組件化的疑惑,這是Vue的缺陷嗎?</a><br />※<a target=_blank href=/p20180111765264080/>前端來防止csrf,這個做法是否有漏洞?</a></p> <p>TAG:<a target=_blank href=/tag/前端開發/>前端開發</a> | <a target=_blank href=/tag/JavaScript/>JavaScript</a> | <a target=_blank href=/tag/前端工程師/>前端工程師</a> | <a target=_blank href=/tag/Vuejs/>Vuejs</a> | </p> <!-- AddThis Advanced Settings above via filter on the_content --><!-- AddThis Advanced Settings below via filter on the_content --><!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons above via filter on the_content --><!-- AddThis Share Buttons below via filter on the_content --><div class="at-below-post addthis_tool" data-url="https://www.getit01.com/p20180111050340251/"></div><!-- AddThis Share Buttons generic via filter on the_content --></div> <script src="/ce.js"></script> <div class="clear"></div> </div> </div> <div class="clear"></div> </div> </div> <div class="clear"></div> <div id="footer"> <div class="copyright"> <p> 一點新知 <a href="https://www.getit01.com/"><strong> GetIt01 </strong></a> <div style="display:none"><script src="https://s13.cnzz.com/z_stat.php?id=1270562218&web_id=1270562218" language="JavaScript"></script></div> <br /> </p> </div> </div> </div> <!--gototop--> <div id="tbox"> <a target="_blank" id="fb" href="https://www.facebook.com/sharer.php?u=https://www.getit01.com/p20180111050340251/"></a> </div> <script data-cfasync="false" type="text/javascript">if (window.addthis_product === undefined) { window.addthis_product = "wpp"; } if (window.wp_product_version === undefined) { window.wp_product_version = "wpp-6.1.1"; } if (window.wp_blog_version === undefined) { window.wp_blog_version = "4.8.22"; } if (window.addthis_share === undefined) { window.addthis_share = {}; } if (window.addthis_config === undefined) { window.addthis_config = {"data_track_clickback":true,"ignore_server_config":true,"ui_atversion":"300"}; } if (window.addthis_layers === undefined) { window.addthis_layers = {}; } if (window.addthis_layers_tools === undefined) { window.addthis_layers_tools = [{"share":{"counts":"each","numPreferredServices":5,"mobile":false,"position":"left","theme":"transparent"},"sharedock":{"counts":"each","numPreferredServices":5,"mobileButtonSize":"large","position":"bottom","theme":"transparent"}},{"sharetoolbox":{"numPreferredServices":5,"counts":"each","size":"32px","style":"fixed","shareCountThreshold":0,"elements":".addthis_inline_share_toolbox_vh9e,.at-above-post"}}]; } else { window.addthis_layers_tools.push({"share":{"counts":"each","numPreferredServices":5,"mobile":false,"position":"left","theme":"transparent"},"sharedock":{"counts":"each","numPreferredServices":5,"mobileButtonSize":"large","position":"bottom","theme":"transparent"}}); window.addthis_layers_tools.push({"sharetoolbox":{"numPreferredServices":5,"counts":"each","size":"32px","style":"fixed","shareCountThreshold":0,"elements":".addthis_inline_share_toolbox_vh9e,.at-above-post"}}); } if (window.addthis_plugin_info === undefined) { window.addthis_plugin_info = {"info_status":"enabled","cms_name":"WordPress","plugin_name":"Share Buttons by AddThis","plugin_version":"6.1.1","plugin_mode":"WordPress","anonymous_profile_id":"wp-465109ee2f0e70a26b602727e258dac0","page_info":{"template":"posts","post_type":""},"sharing_enabled_on_post_via_metabox":false}; } (function() { var first_load_interval_id = setInterval(function () { if (typeof window.addthis !== 'undefined') { window.clearInterval(first_load_interval_id); if (typeof window.addthis_layers !== 'undefined' && Object.getOwnPropertyNames(window.addthis_layers).length > 0) { window.addthis.layers(window.addthis_layers); } if (Array.isArray(window.addthis_layers_tools)) { for (i = 0; i < window.addthis_layers_tools.length; i++) { window.addthis.layers(window.addthis_layers_tools[i]); } } } },1000) }()); </script><script type='text/javascript' src='https://s7.addthis.com/js/300/addthis_widget.js?ver=4.8.22#pubid=wp-465109ee2f0e70a26b602727e258dac0'></script> <script type='text/javascript' src='https://www.getit01.com/wp-content/themes/Qu/js/loostrive.js?ver=1.0'></script> <script type='text/javascript' src='https://www.getit01.com/wp-includes/js/wp-embed.min.js?ver=4.8.22'></script> </body></html> <!-- Dynamic page generated in 0.105 seconds. --> <!-- Cached page generated by WP-Super-Cache on 2024-03-13 04:32:57 --> <!-- super cache -->