自定义Core Component中Assigns无法绑定属性问题排查
问题描述
在LiveView中实现了一个复选框组自定义组件,新增:meow属性后,在input函数中访问该属性始终返回nil,直接调用assigns.meow会触发KeyError。
代码示例
core_components.ex
@doc """ 生成用于多选的复选框组。 ## 示例 <.checkgroup field={@form[:genres]} label="Genres" options={[{"Fantasy", "fantasy"}, {"Science Fiction", "sci-fi"}]} selected={[{"Fantasy", "fantasy"}]} /> """ attr :id, :any attr :name, :any attr :label, :string, default: nil attr :field, Phoenix.HTML.FormField, doc: "从表单获取的表单字段结构体,例如:@form[:genres]" attr :errors, :list attr :required, :boolean, default: false attr :rest, :global, include: ~w(form readonly) attr :class, :string, default: nil attr :options, :list, default: [], doc: "传递给Phoenix.HTML.Form.options_for_select/2的选项" attr :selected, :list, default: [], doc: "当前选中的选项,用于标记哪些复选框被勾选" attr :meow, :any, default: "meow", doc: "一个超酷的新属性!" def checkgroup(assigns) do new_assigns = assigns |> assign(:multiple, true) |> assign(:type, "checkgroup") input(new_assigns) end def input(%{type: "checkgroup"} = assigns) do ~H""" <p> <%= "Cat says: #{@meow}" %> </p> <div class="mt-2"> <%= for opt <- @options do %> <div class="relative flex gap-x-3"> <div class="flex h-6 items-center"> <input id={opt.id} name={@name} type="checkbox" value={opt.id} checked={opt in @selected} disabled={false} /> </div> <div class="text-sm leading-6"> <label for={opt.id} ><%= opt.name %></label> </div> </div> <% end %> </div> """ end
错误场景
使用组件时传入meow={[1,2]}:
<.checkgroup field={@form[:my_form]} label="SUper question!" options={@sounds} selected={@selected_sounds} meow={[1, 2]} required />
渲染时触发KeyError,错误信息显示:meow存在于__given__中,但在assigns顶层无法找到。
问题原因
Phoenix组件的attr定义是与对应函数绑定的:
:meow属性只在checkgroup/1函数的attr声明里定义了,但input/1函数没有声明该属性。- 当调用
input(new_assigns)时,Phoenix会自动过滤掉input/1函数未声明的属性,导致:meow不会被保留在input函数的assigns中,因此访问@meow会返回nil,直接取assigns.meow会触发KeyError。
修复方案
需要在input/1函数中也声明:meow属性,确保该属性能被传递到渲染模板中:
修改input/1函数的attr部分(如果input是处理多种组件类型的通用函数,可以把:meow的attr放在通用定义里,或者针对checkgroup类型单独处理):
# 在input函数上方添加attr声明 attr :meow, :any, default: "meow", doc: "一个超酷的新属性!" def input(%{type: "checkgroup"} = assigns) do ~H""" <p> <%= "Cat says: #{@meow}" %> </p> <div class="mt-2"> <%= for opt <- @options do %> <div class="relative flex gap-x-3"> <div class="flex h-6 items-center"> <input id={opt.id} name={@name} type="checkbox" value={opt.id} checked={opt in @selected} disabled={false} /> </div> <div class="text-sm leading-6"> <label for={opt.id} ><%= opt.name %></label> </div> </div> <% end %> </div> """ end
如果input函数是处理多种组件类型的通用函数,也可以用assign_new在input函数中确保:meow存在:
def input(%{type: "checkgroup"} = assigns) do assigns = assign_new(assigns, :meow, fn -> "meow" end) ~H""" <p> <%= "Cat says: #{@meow}" %> </p> <!-- 其余渲染代码 --> """ end
推荐第一种方案,因为它符合Phoenix组件的规范,明确声明组件接收的属性,提高代码可读性。
内容的提问来源于stack exchange,提问作者Flame_Phoenix
相关产品推荐
相关产品推荐

