yii2 widget必须重写init()和run()方法:init()负责初始化(校验、赋值、注册资源),run()必须return字符串;参数需通过public属性声明并用arrayhelper::merge()合并;必须用widget::widget()调用而非new;view路径默认为@ app/views/widget-name/;registerjs需加唯一$key防重复。

Widget类继承必须重写init()和run()方法
Yii2的Widget不是靠配置驱动,而是靠两个核心钩子:初始化时的init()和渲染时的run()。漏掉任一个,小部件要么不执行逻辑,要么直接报Method not found错误。
常见错误是只写run(),以为返回HTML就够了——但init()里常要处理$this->options、$this->view注入或提前校验参数,跳过它容易导致后续$this->view->registerJs()失败或$this->options未合并默认值。
-
init()里别做输出,只做准备:赋值、校验、注册资源 -
run()必须有return,且返回string(不能echo) - 如果需要接收参数,统一通过
public $xxx属性声明,并在init()中用ArrayHelper::merge()合并默认值
使用Widget::widget()时传参必须用数组语法
调用自定义Widget不能像普通PHP类那样new MyWidget(['title' => 'xxx']),必须走Yii的工厂方式:MyWidget::widget(['title' => 'xxx'])。否则$this->getView()为空、$this->id不生成、事件绑定失效。
这个限制源于Yii2 Widget生命周期依赖Widget::begin()/Widget::end()或Widget::widget()触发的createObject()流程。手动new会绕过整个上下文初始化。
- 所有参数都进数组,包括
'class'以外的键,如'title'、'items'、'options' - 避免在数组里传匿名函数——序列化失败,会导致
Serialization of Closure is not allowed - 若需动态内容,改用
View::renderFile()配合use传变量,而非把闭包塞进widget参数
复用Widget时要注意view路径和命名空间冲突
Widget内部调用$this->render('index')时,默认找@app/views/widget-name/index.php,而不是当前Controller的view目录。很多人误以为它会自动继承调用处的view上下文,结果报View file not found。
更隐蔽的问题是:多个Widget同名(比如都叫AlertWidget)但分散在不同模块,若没严格隔离命名空间或别名,use错一个就会加载到旧版本,样式/逻辑静默失效。
- 在Widget类顶部明确写
use yii\base\Widget;,别依赖自动导入 - view文件路径用
__DIR__ . '/views/index.php'硬编码更可控,尤其跨模块复用时 - 发布到公共组件库前,把
namespace设为带vendor前缀的唯一值,例如mycompany\widgets\AlertWidget
性能陷阱:Widget内频繁调用registerJs/registerCss会重复插入
同一个页面多次使用同一Widget(比如列表页每行一个ActionButtonWidget),如果每个实例都在init()里调用$this->view->registerJs("xxx"),JS代码会被插入N次,轻则浪费带宽,重则触发重复绑定事件(点击一次执行N次)。
根本原因是registerJs默认无key去重,它只按内容字符串简单判断是否已注册——而动态生成的JS(含ID、时间戳等)每次都不一样。
- 给
registerJs加唯一$key参数:$this->view->registerJs($js, View::POS_READY, 'my-button-init') - 静态资源(如通用工具函数)优先提前提到Layout里,Widget只负责调用
- 避免在
run()里register,那里更适合拼HTML;资源注册全收口到init()
Widget复用真正卡点不在写法,而在上下文隔离和资源生命周期管理——这两块松动,越复用越不可控。











