💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 组件 组件是可重用代码的基础。 他们使你的工作更容易,并允许你从社区工作中获利。 组件是美好的。 这章会学到以下几点 1、如何写组件 2、什么是信号 3、如何发送弹出消息 4、如何使用AJAX 组件是可呈现对象。 它可以是一个形式,菜单,民意调查等。在一个页面内可以有你想要的任何组件。 在https://addons.nette.org/cs, 你可以找到来自Nette Framework社区志愿者的开源组件。 你可以找到一个组件的例子,它在示例Fifteen中的一个页面中使用。 组件是Nette \ Application \ UI \ Control类的后代 ~~~ use Nette\Application\UI\Control; class PollControl extends Control { } ~~~ 当谈论组件时,我们通常指的是Control类的后代。 组件基本上是框架术语中的控制。 ## 模板 组件的模板有工厂。 它创建一个模板,传递几个变量(如下所示)并注册默认过滤器。 渲染是我们的工作,它是在方法render()中完成的。 这是我们选择将从中加载模板的文件的位置。 render()方法也是注册模板中使用的变量的正确位置。 模板可以放在与组件相同的文件夹中,并具有相同的文件名。 ~~~ public function render() { $template = $this->template; $template->setFile(dirname(__FILE__) . '/PollControl.latte'); //插入一些参数到模板 $template->param = 'some value'; // 渲染它 $template->render(); } ~~~ 可以将参数从模板传递到render()。 ~~~ {control poll $poll} ~~~ ~~~ // PollControl.php public function render($poll) { ... } ~~~ ~~~ // PollPresenter.php protected function createComponentPoll() { ... } ~~~ ## 链接 方法link()用于链接到信号。 如果我们在组件的模板中,并且目标是控制器,则使用模板中的宏{link}或宏{plink}渲染链接。 在组件中使用: ~~~ $url = $this->link('click!', $x, $y); ~~~ 在模板中使用: ~~~ <a n:href="click! $x, $y"> ... </a> ~~~ ## 弹出信息 组件具有自己的闪存消息存储,而与呈现者无关。 这些消息通知操作的结果,然后是重定向。 发送由flashMessage方法完成。 第一个参数是消息的文本,第二个(可选)参数是其类型(错误,警告,信息等)。 方法flashMessage()返回一个可以传递信息的弹出消息的实例。 例如: ~~~ public function deleteFormSubmitted(Form $form) { ... 使用模型删除记录... // 传递弹出消息 $this->flashMessage('Item was deleted.'); $this->redirect(...); // 跳转 } ~~~ 模板在变量$flashes中自动接收此消息。 此变量包含一个对象数组(stdClass),它包含属性消息,类型,并且可以包含上面提到的额外信息。 例如: ~~~ {foreach $flashes as $flash} <div class="flash {$flash->type}">{$flash->message}</div> {/foreach} ~~~ 如果保存了flashMessage()并发生重定向,则模板中将存在相同的参数$ flashes。 然后,邮件在接下来的三秒内保持活动状态 - 以防止在页面未成功加载并且用户刷新请求的情况下丢失。 如果某人连续两次刷新页面(F5),则弹出信息仍然存在,如果他/她单击其他位置,它就会消失。 ## 信号(子请求) 信号(子请求)是与服务器在正常视图级别下的通信。信号是没有改变视图的动作。只有控制器可以更改视图,因此组件仅与信号一起工作。 $ component-> link()链接到一个信号,$ presenter-> link()通常链接到一个视图(除非使用感叹号 - $ presenter-> link('signal!'))。但是一个组件可以调用$ component-> presenter-> link('view')。 一个信号导致像原始请求(除了AJAX)的页面重新加载,并调用方法signalReceived($ signal),它的类Nette \ Application \ UI \ Component中的默认实现尝试调用一个由句柄{Signal }。进一步的处理依赖于给定的对象。作为组件(即控制和呈现器)的后代的对象试图用相关参数调用句柄{Signal}。 换句话说:采用方法句柄{Signal}的定义,并且在请求中接收的所有参数都与方法的参数相匹配。这意味着来自URL的参数id与方法的参数$ id匹配,某事到$ something等。如果方法不存在,则signalReceived方法抛出异常。 示例信号处理程序: ~~~ public function handleClick($x, $y) { if (!$this->isClickable($x, $y)) { throw new Nette\Application\UI\BadSignalException('Action not allowed.'); } // ... 处理信号 ... } ~~~ 信号可以由任何组件接收,如果它连接到组件树,实现接口ISignalReceiver对象的控制器。 信号的主要接收器是呈现器和可视化组件扩展控制。 信号是对象的标志,它必须做某事 - 在来自用户的投票中的轮询计数,具有新闻的框必须展开,形式被发送并且必须处理数据等等。 信号总是在当前控制器和视图上调用,因此不可能将其指向其他地方。 信号的URL使用方法Component :: link()创建。作为参数$ destination我们传递string {signal}!和$ args我们想传递给信号处理程序的参数数组。信号参数附加到当前控制器/视图的URL。 URL中的参数?do确定调用的信号。 其格式为{signal}或{signalReceiver} - {signal}。 {signalReceiver}是控制器中组件的名称。这就是为什么连字符(不准确的破折号)不能出现在组件名称中 - 它用于划分组件名称和信号,但是可以组成几个组件。 方法isSignalReceiver()验证组件(第一个参数)是否是信号(第二个参数)的接收器。第二个参数可以省略 - 然后它找出组件是否是任何信号的接收器。如果第二个参数为TRUE,它将验证组件或其后代是否是信号的接收器。 在任何阶段前面的句柄{Signal}可以通过调用processSignal()方法手动执行信号,processSignal()负责信号执行。接收器组件(如果没有设置它是演示者本身)并发送它的信号。 例如: ~~~ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) { $this->processSignal(); } ~~~ 信号过早执行,不会再次调用。 ### 子请求与请求 信号和请求之间的差异: * 子请求保留所有组件 * 一个请求只保留持久性组件 ## 无效和摘要 在信号的处理期间,可能发生需要重新呈现组件的改变。 这些方法:redrawControl()和isControlInvalid()。 他们是使用AJAX与Nette的基本元素。 Nette还提供了更高的有效性粒度,只有在组件级别,使用片段。 代码段可以逐个失效(任何组件可以根据需要拥有尽可能多的代码段)。 如果整个组件无效,那么它的每个片段也会失效。 如果某个组件的任何子组件(组件)无效,则该组件被视为无效。 ## 持久参数 通常需要在组件中保留某个参数,以便对组件进行全部处理。 它可以是例如分页中的页的编号。 此参数应使用注释@persistent标记为persistent。 ~~~ class PollControl extends Control { /** @persistent */ public $page = 1; ... } ~~~ 此参数将作为GET参数在每个链接中自动传递,直到用户离开具有此组件的页面。 永远不要盲目地相信永久性参数,因为它们可以容易地伪造(通过覆盖URL)。 例如,验证页码是否在正确的时间间隔内。 ## 高级使用组件 组件大多是可渲染的。 但也存在不可描述的组件。 一些组件可能有一些不是后代。 Nette Framework为所有这些类型的组件引入了几个类和接口。 对象继承允许我们像现实世界中一样具有类的层次结构。 我们可以通过扩展创建新类。 这些扩展类是原始类的后代,并继承其参数和方法。 扩展类可以向继承的类添加自己的参数和方法。 需要知识类层次结构才能正确理解事物的工作原理。 ~~~ Nette\ComponentModel\Component { IComponent } | +- Nette\ComponentModel\Container { IContainer } | +- Nette\Application\UI\Component { ISignalReceiver, IStatePersistent } | +- Nette\Application\UI\Control { IPartiallyRenderable } | +- Nette\Application\UI\Presenter { IPresenter } ~~~ ### Nette\ComponentModel\IComponent 接口Nette \ ComponentModel \ IComponent必须由每个组件实现。 它需要方法getName(),它返回它的名称,getParent()返回它的父级。 可以使用方法setParent()设置名称和父项 - 第一个参数是父项,第二个参数是名称。 ### Nette\ComponentModel\Component Nette \ ComponentModel \ Component是IComponent的标准实现。 它是所有组件的共同祖先,包括表单元素。 它有几种方法来遍历: lookup($ type)在层次结构中查找给定类或接口的对象。 例如$ component-> lookup('Nette \ Application \ UI \ Presenter')返回presenter,如果组件绑定到它(甚至在层次结构中)。 lookupPath($ type)返回path - 通过连接$ type的当前和组件之间的所有组件的名称而创建的字符串。 例如$ component-> lookupPath('Nette \ Application \ UI \ Presenter')返回组件向演示者的唯一标识符。 ### Nette\ComponentModel\IContainer 父组件不仅实现了IComponent,还实现了Nette \ ComponentModel \ IContainer。 包含用于在组件上添加,删除,获取和迭代的方法的接口。 这样的组件可以创建层次结构 - 控制器可以包含表单,这些表单可以包含表单输入。 组件的整个树层次由IContainer对象和IComponent树叶的分支创建。 ### Nette\ComponentModel\Container Nette \ ComponentModel \ Container是IContainer接口的标准实现。 它是例如一个或多个控件和控制器的祖先。 它提供了正确添加,获取和删除对象的方法,当然还有对其内容的迭代。 尝试调用未定义的子进程调用factory createComponent($ name)。 方法createComponent($ name)调用当前组件中的方法createComponent <component name>,它将组件的名称作为参数传递。 创建的组件然后作为其子进程传递到当前组件。 我们称之为theese组件工厂,它们可以在继承自Container的类中实现。 ### Nette\Application\UI\Component 类Nette \ Application \ UI \ Component是演示者中使用的所有组件的祖先。 演示者中的组件是由演示者在其生命周期期间保持的对象。 它们具有彼此影响的能力,将它们的状态保存到URL中并响应用户命令(信号),并且不必是可呈现的。 ### Nette\Application\UI\Control 控件是一个可渲染组件。 它是一个可重用的Web应用程序的一部分,这是整个章节。 当谈论组件这是我们通常想到的类。 它记住它的一部分应该在AJAX请求中呈现,我们已经谈到了。 控件不是表示网页的可视部分,而是逻辑部分。 可以重复地,有条件地并且每次使用不同的模板来呈现它。 ### 组件树 如果给定不同的名称,可以在页面上多次呈现相同的组件(这可以动态地完成)。 父代可以是控制器,组件或实现IContainer的其他对象。 层次结构可以如下所示: ~~~ Nette\Application\UI\Presenter { root of tree of components is always a presenter } | --Nette\Application\UI\Control { implements IContainer => can be parent } | --Nette\ComponentModel\Component | --Nette\ComponentModel\Component { does not implement IContainer => cannot be parent } | --Nette\Application\UI\Control | --Nette\ComponentModel\Component ~~~ ## 延迟组成 Nette中的组件模型提供了非常动态的树工作流(组件可以删除,添加,移动)。 在创建组件时,依赖于knowing parent(在构造函数中)将是一个错误。 在大多数情况下,父组件在创建组件时并不知道。 ~~~ $control = new NewsControl; // ... $parent->addComponent($control, 'shortNews'); ~~~ ## 监控更改 如何找出组件何时添加到控制器的树? 观看父级的更改是不够的,因为父级的父级可能已添加到控制器。 方法监视器($ type)是在这里帮助。 每个组件都可以监视任意数量的类和接口。 通过调用附加的方法($ obj)(detached($ obj)个别)报告添加或删除,其中$ obj是受监视类的对象。 示例:类UploadControl,表示在Nette \ Forms中上传文件的表单元素,必须将表单的属性类型设置为值multipart / form-data。 但在创建对象的时候,它不必附加到任何形式。 什么时候修改窗体? 解决方案很简单 - 我们在构造函数中创建一个监视请求: ~~~ class UploadControl extends Nette\Forms\Controls\BaseControl { public function __construct($label) { $this->monitor('Nette\Forms\Form'); // ... } // ... } ~~~ 并且在表单可用时调用附加的方法: ~~~ protected function attached($form) { parent::attached($form); if ($form instanceof Nette\Forms\Form) { $form->getElementPrototype()->enctype = 'multipart/form-data'; } } ~~~ 使用查找监视和查找组件或路径是非常精确优化的,以获得最佳性能。 ## Iterating over children 有一个方法getComponents($ deep = FALSE,$ type = NULL)。 第一个参数确定是否应该深入(递归地)查找组件。 使用TRUE,它不仅迭代所有它的孩子,而且它的所有孩子的孩子等。第二个参数服务器作为一个可选的过滤器通过类或接口。 例如,这是表单验证如何在内部执行的方式: ~~~ $valid = TRUE; foreach ($form->getComponents(TRUE, 'Nette\Forms\IControl') as $control) { if (!$control->getRules()->validate()) { $valid = FALSE; break; } } ~~~