Skip to content
全部文档

wire:stream

Livewire 允许你通过 wire:stream API,在请求完成之前将内容流式传输到网页。这对 AI 聊天机器人等在生成过程中流式返回响应的场景极为有用。

WARNING

与 Laravel Octane 不兼容

目前 Livewire 不支持将 wire:stream 与 Laravel Octane 一起使用。

为演示 wire:stream 最基本的功能,下面是一个简单的 CountDown 组件:按下按钮后,向用户显示从「3」到「0」的倒计时:

php
use Livewire\Component;

class CountDown extends Component
{
    public $start = 3;

    public function begin()
    {
        while ($this->start >= 0) {
            // Stream the current count to the browser...
            $this->stream(  // [tl! highlight:4]
                to: 'count',
                content: $this->start,
                replace: true,
            );

            // Pause for 1 second between numbers...
            sleep(1);

            // Decrement the counter...
            $this->start = $this->start - 1;
        };
    }

    public function render()
    {
        return <<<'HTML'
        <div>
            <button wire:click="begin">Start count-down</button>

            <h1>Count: <span wire:stream="count">{{ $start }}</span></h1> <!-- [tl! highlight] -->
        </div>
        HTML;
    }
}

用户按下「Start count-down」时,从用户视角发生的情况如下:

  • 页面上显示「Count: 3」
  • 他们按下「Start count-down」按钮
  • 过一秒后显示「Count: 2」
  • 该过程持续到显示「Count: 0」

以上全部发生在发往服务器的单次网络请求期间。

按下按钮时,从系统视角发生的情况如下:

  • 向 Livewire 发送请求以调用 `begin()` 方法
  • 调用 `begin()` 方法并开始 `while` 循环
  • 调用 `$this->stream()`,立即开始向浏览器发送「流式响应」
  • 浏览器收到流式响应,指示在组件中查找带有 `wire:stream="count"` 的元素,并用收到的载荷替换其内容(首次流式数字为「3」)
  • `sleep(1)` 使服务器暂停一秒
  • 重复 `while` 循环,每秒流式传输一个新数字,直到 `while` 条件为假
  • 当 `begin()` 运行完毕且所有计数都已流式传到浏览器后,Livewire 完成请求生命周期,渲染组件并向浏览器发送最终响应

流式传输聊天机器人响应

wire:stream 的常见用例是:从支持流式响应的 API(如 OpenAI 的 ChatGPT)接收时,将聊天机器人响应流式传输出来。

下面示例用 wire:stream 实现类似 ChatGPT 的界面:

php
use Livewire\Component;

class ChatBot extends Component
{
    public $prompt = '';

    public $question = '';

    public $answer = '';

    function submitPrompt()
    {
        $this->question = $this->prompt;

        $this->prompt = '';

        $this->js('$wire.ask()');
    }

    function ask()
    {
        $this->answer = OpenAI::ask($this->question, function ($partial) {
            $this->stream(to: 'answer', content: $partial); // [tl! highlight]
        });
    }

    public function render()
    {
        return <<<'HTML'
        <div>
            <section>
                <div>ChatBot</div>

                @if ($question)
                    <article>
                        <hgroup>
                            <h3>User</h3>
                            <p>{{ $question }}</p>
                        </hgroup>

                        <hgroup>
                            <h3>ChatBot</h3>
                            <p wire:stream="answer">{{ $answer }}</p> <!-- [tl! highlight] -->
                        </hgroup>
                    </article>
                @endif
            </section>

            <form wire:submit="submitPrompt">
                <input wire:model="prompt" type="text" placeholder="Send a message" autofocus>
            </form>
        </div>
        HTML;
    }
}

上例中发生的情况如下:

  • 用户在标为「Send a message」的文本字段中输入,向聊天机器人提问。
  • 他们按下 [Enter] 键。
  • 向服务器发送网络请求,将消息设到 `$question` 属性,并清空 `$prompt` 属性。
  • 响应返回浏览器并清空输入。由于调用了 `$this->js('...')`,会触发新请求到服务器调用 `ask()` 方法。
  • `ask()` 方法调用 ChatBot API,并通过回调中的 `$partial` 参数接收流式响应片段。
  • 每个 `$partial` 都会流式传到页面上带有 `wire:stream="answer"` 的元素中,让答案逐步呈现给用户。
  • 收到完整响应后,Livewire 请求结束,用户得到完整答案。

替换与追加

$this->stream() 向元素流式传输内容时,可以告诉 Livewire:用流式内容替换目标元素的内容,或追加到现有内容之后。

替换或追加取决于场景,两者都可能合适。例如,流式传输聊天机器人响应时通常希望追加(因此这也是默认行为)。而在显示倒计时时,替换更合适。

可通过向 $this->stream 传入布尔值的 replace: 参数来配置:

php
// Append contents...
$this->stream(to: 'target', content: '...');

// Replace contents...
$this->stream(to: 'target', content: '...', replace: true);

也可以在目标元素级别通过添加或去掉 .replace 修饰符来指定追加/替换:

blade
// Append contents...
<div wire:stream="target">

// Replace contents...
<div wire:stream.replace="target">

参考

blade
wire:stream="name"

修饰符

修饰符说明
.replace替换元素内容,而不是追加