iT邦幫忙

2021 iThome 鐵人賽

DAY 25
0
自我挑戰組

Ruby on Rails JS系列 第 25

Ruby on Rails 局部渲染(Partial Render)

局部渲染是⼀種在 Rails 專案很常⾒的程式碼整理⼿法之⼀。在前⼀個章節整理表單
的時候,有⽤到了這樣的寫法:

<%= render "form" %>
其實完整的寫法是這樣寫:
<%= render partial: "form" %>

但⽤前⾯簡單的寫法在這邊也是可以的。這⾏的意思是會去同⼀個⽬錄找 _form
這個檔案,並且把檔案內容安插在這個地⽅。注意這個檔案必須要是底線開頭的,
否則會出現 ActionView::MissingTemplate 的錯誤訊息:

通常局部渲染適⽤於「可以重複使⽤」的程式碼,像是在上個章節的表單就是⼀
例。

較好的設計

雖然 <%= render "form" %> 這樣短短⼀⾏很容易⽤,但這樣不⾒得是個好的設
計。⼀個容易重複使⽤的局部樣版,不應該依賴滿天⾶的實體變數。舉個例⼦來
說,如果想要做⼀個叫做橫幅廣告的局部樣版,可能會這樣寫(檔案名稱
_banner.html.erb ):

<div class="advertisement">
<div>廣告</div>
<div> <%= @content %> </div>
</div>
使⽤的時候這樣⽤:
<%= render "banner" %>

也就是說,這個 _banner.html.erb 會⾃⼰去空氣中抓看看有沒有 @content
這個實體變數給它,有的話就印出來。但事實上不是每個⾴⾯都有這東⻄可以抓,
所以不⾒得容易重複使⽤。比較建議的設計會是這樣:

<div class="advertisement">
<div>廣告</div>
<div> <%= content %> </div>
</div>
讓這個局部樣版裡只有普通的區域變數,然後在使⽤它的時候,把值傳主動傳給
它:
<%= render partial: "banner", locals: {content: "我是廣告的內容"} %>

注意:這樣的寫法 partial 參數不能省略。
這個局部樣版就會得到⼀個 content 的區域變數。這樣做似乎變得之前更⿇煩,
但這樣的設計可以讓這個局部樣版變得更像⼀個「元件」,它被動的等著你餵它資
料,⽽不是⾃⼰伸⼿去空中抓,這樣⼀來不管在哪個⾴⾯都可適⽤。
上⾯這個寫法可以再精簡成這樣:

<%= render "banner", content: "我是廣告的內容" %>

魔術 render
在上⼀章的「候選⼈列表」的程式碼中,中間有⼀段 each 迴圈,不斷的印出資
料:

<h1>候選⼈列表</h1>
..[略]..
<tbody>
<% @candidates.each do |candidate| %>
<tr>
<td><%= link_to "投給這位", "#" %></td>
<td><%= candidate.name %>(年齡:<%= candidate.age %> 歲)</td>
<td><%= candidate.party %></td>
<td><%= candidate.politics %></td>
<td><%= candidate.votes %></td>
<td>
<%= link_to "編輯", edit_candidate_path(candidate) %>
15 Layout,Render 與 View Helper
232
<%= link_to "刪除", candidate_path(candidate), method: "de
lete", data: { confirm: "確認刪除" } %>
</td>
</tr>
<% end %>
</tbody>
</table>
這段如果使⽤局部樣版來整理,可以把中間那段抽出來:
<h1>候選⼈列表</h1>
..[略]..
<tbody>
<%= render "candidate" %>
</tbody>
</table>
然後在 app/views/candidates ⽬錄底下新增⼀個檔案
_candidate.html.erb :
<% @candidates.each do |candidate| %>
<tr>
<td><%= link_to "投給這位", vote_candidate_path(candidate), metho
d: "post", data: { confirm: "確認要投給這位候選⼈嗎?!" }, class: "btn
btn-danger btn-xs" %></td>
<td><%= candidate.name %>(年齡:<%= candidate.age %> 歲)</td>
<td><%= candidate.party %></td>
<td><%= candidate.politics %></td>
<td><%= candidate.votes %></td>
<td>
<%= link_to "編輯", edit_candidate_path(candidate) %>
<%= link_to "刪除", candidate_path(candidate), method: "delete"
, data: { confirm: "確認刪除" } %>
</td>
</tr>
<% end %>

這樣的改法感覺沒什麼了不起的,就是把原來的內容整個剪到另⼀個檔案⽽已,它
需要的 @candidates 實體變數也還是⾃⼰往空中抓,前⾯才剛講這樣不是好的設
計。的確,如果只是這樣的話其實也不需要改了。Rails 針對這樣的⽤法,有提供
render ⽅法⼀個 collection 參數:

<%= render partial: "candidate", collection: @candidates %>

然後 _candidate.html.erb 檔案的內容就可以把外層的 each 迴圈拿掉,變成
這樣:

<tr>
<td><%= link_to "投給這位", vote_candidate_path(candidate), metho
d: "post", data: { confirm: "確認要投給這位候選⼈嗎?!" }, class:"btn
btn-danger btn-xs" %></td>
<td><%= candidate.name %>(年齡:<%= candidate.age %> 歲)</td>
<td><%= candidate.party %></td>
<td><%= candidate.politics %></td>
<td><%= candidate.votes %></td>
<td>
<%= link_to "編輯", edit_candidate_path(candidate) %>
<%= link_to "刪除", candidate_path(candidate), method: "delete"
, data: { confirm: "確認刪除" } %>
</td>
</tr>

因為傳了 collection 參數的關係,在局部樣版裡即使拿掉了 each 迴圈,它還
是可以正常運作,⽽且呈現的畫⾯還是跟原來的⼀樣。這樣⼀來,在
_candidate.html.erb 檔案裡的都只剩區域變數,不需要再依賴空中的實體變
數。
⽽且 render ⽅法還可以再短⼀點,把這⼀⾏:

<%= render partial: "candidate", collection: @candidates %>

直接改成這樣:

<%= render @candidates %>

_candidate.html.erb 檔案不需要改,整個還是可以正常運作。短短⼀⾏,⽽且
還不⽤寫迴圈就可以達到跟原來⽤ each 迴圈⼀樣的效果,很神奇吧!
但這樣的寫法其實有點過於魔術,⽽且依賴不少 Rails 裡的「慣例」。如果要讓

<%= render @candidates %> 可以正常運作的話,需要完成以下幾件事:
  1. 局部樣版的檔名必須是那包資料的「單數」,以上⾯這個例⼦來說,那包資料
    叫做 @candidates ,所以 Partial 的檔名就是 _candidate.html.erb 。
  2. 局部樣版檔案必須⽽且放在對的位置,以這個例⼦來說,檔案是放在
    app/views/candidates ⽬錄裡。
  3. 局部樣版裡不需要寫迴圈(寫了反⽽會多跑⼀層迴圈),裡⾯⽤到的區域變數
    必須是單數,例如 candidate 。
    如果沒有按照這些慣例就會出現找不到檔案或路徑的錯誤訊息。

參考資料

[為你自己學Ruby on Rails]https://railsbook.tw/chapters/08-ruby-basic-4.html


上一篇
Ruby on Rails layout
下一篇
Ruby on Rails View Helper
系列文
Ruby on Rails JS29

尚未有邦友留言

立即登入留言