archtechx/airwire

一个轻量级的全栈组件层,不指定你的前端框架

资助包维护!
stancl

v0.1.4 2021-05-22 13:51 UTC

This package is auto-updated.

Last update: 2024-09-08 19:12:48 UTC


README

注意: 当前开发已暂停。在发布 Lean Admin 后将恢复。

Airwire

一个轻量级的全栈组件层,不指定你的前端框架

演示

介绍

Airwire 是您 Laravel 代码和 JavaScript 之间的一个薄层。

它允许您编写类似 Livewire 风格的 OOP 组件,如下所示

class CreateUser extends Component
{
    #[Wired]
    public string $name = '';

    #[Wired]
    public string $email = '';

    #[Wired]
    public string $password = '';

    #[Wired]
    public string $password_confirmation = '';

    public function rules()
    {
        return [
            'name' => ['required', 'min:5', 'max:25', 'unique:users'],
            'email' => ['required', 'unique:users'],
            'password' => ['required', 'min:8', 'confirmed'],
        ];
    }

    #[Wired]
    public function submit(): User
    {
        $user = User::create($this->validated());

        $this->meta('notification', __('users.created', ['id' => $user->id, 'name' => $user->name]));

        $this->reset();

        return $user;
    }
}

然后,它将生成一个 TypeScript 定义,如下所示

interface CreateUser {
    name: string;
    email: string;
    password: string;
    password_confirmation: string;
    submit(): AirwirePromise<User>;
    errors: { ... }

    // ...
}

Airwire 将将这两部分连接起来。您可以使用任何前端框架(如果有的话),Airwire 将简单地转发调用并同步前端和后端的状态。

Airwire 的最基本用法如下所示

let component = Airwire.component('create-user')

console.log(component.name); // your IDE knows that this is a string

component.name = 'foo';

component.errors; // { name: ['The name must be at least 10 characters.'] }

// No point in making three requests here, so let's defer the changes
component.deferred.name = 'foobar';
component.deferred.password = 'secret123';
component.deferred.password_confirmation = 'secret123';

// Watch all received responses
component.watch(response => {
    if (response.metadata.notification) {
        alert(response.metadata.notification)
    }
})

component.submit().then(user => {
    // TS knows the exact data structure of 'user'
    console.log(user.created_at);
})

安装

需要 Laravel 8 和 PHP 8。

首先通过 composer 安装包

composer require archtechx/airwire

然后进入您的 webpack.mix.js 并注册监视器插件。它会在您更改 PHP 代码时刷新 TypeScript 定义

mix.webpackConfig({
    plugins: [
        new (require('./vendor/archtechx/airwire/resources/js/AirwireWatcher'))(require('chokidar')),
    ],
})

接下来,生成初始 TS 文件

php artisan airwire:generate

这将创建 airwire.tsairwired.d.ts。打开您的 app.ts 并导入前者

import Airwire from './airwire'

如果您有一个 app.js 文件而不是 app.ts 文件,更改文件后缀并更新您的 webpack.mix.js 文件

- mix.js('resources/js/app.js', 'public/js')
+ mix.ts('resources/js/app.ts', 'public/js')

如果您是第一次使用 TypeScript,您还需要在项目根目录中创建一个 tsconfig.json 文件。您可以使用这个来开始

{
  "compilerOptions": {
    "target": "es2017",
    "strict": true,
    "module": "es2015",
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "sourceMap": true,
    "skipLibCheck": true
  },
  "include": ["resources/js/**/*"]
}

到此为止!Airwire 已完全安装。

PHP 组件

创建组件

要创建组件,请运行 php artisan airwire:component 命令。

php artisan airwire:component CreateUser

示例中的命令将在 app/Airwire/CreateUser.php 中创建一个文件。

接下来,在您的 AppServiceProvider 中注册它

// boot()

Airwire::component('create-user', CreateUser::class);

连接属性和方法

如果组件属性和方法使用 #[Wired] 属性(与 Livewire 不同,Livewire 使用 public 可见性用于此),则将与前端共享。

这意味着您的组件可以使用属性(甚至公共属性)而不会与前端共享,除非您显式添加此属性。

class CreateTeam extends Component
{
    #[Wired]
    public string $name; // Shared

    public string $owner; // Not shared

    public function hydrate()
    {
        $this->owner = auth()->id();
    }
}

生命周期钩子

如上例所示,Airwire 有有用的生命周期钩子

public function hydrate()
{
    // Executed on each request, before any changes & calls are made
}

public function dehydrate()
{
    // Executed when serving a response, before things like validation errors are serialized into array metadata
}

public function updating(string $property, mixed $value): bool
{
    return false; // disallow this state change
}

public function updatingFoo(mixed $value): bool
{
    return true; // allow this state change
}

public function updated(string $property, mixed $value): void
{
    // execute side effects as a result of a state change
}

public function updatedFoo(mixed $value): void
{
    // execute side effects as a result of a state change
}

public function changed(array $changes): void
{
    // execute side effects $changes has a list of properties that were changed
    // i.e. passed validation and updating() hooks
}

验证

Airwire 组件默认使用 严格验证。这意味着如果提供的数据无效,则不能进行调用。

要禁用严格验证,将该属性设置为 false

public bool $strictValidation = false;

请注意,禁用严格验证意味着您必须完全负责在执行任何可能危险的操作之前(如数据库查询)验证所有传入的输入。

public array $rules = [
    'name' => ['required', 'string', 'max:100'],
];

// or ...
public function rules()
{
    return [ ... ];
}

public function messages()
{
    return [ ... ];
}

public function attributes()
{
    return [ ... ];
}

自定义类型

Airwire 支持自定义 DTO。只需告诉它如何解码(传入请求)和编码(传出响应)数据即可

Airwire::typeTransformer(
    type: MyDTO::class,
    decode: fn (array $data) => new MyDTO($data['foo'], $data['abc']),
    encode: fn (MyDTO $dto) => ['foo' => $dto->foo, 'abc' => $dto->abc],
);

这不需要修改 DTO 类,并且它适用于任何扩展该类的类。

模型

默认包含模型类型转换器。它使用 toArray() 方法生成模型的 JSON 友好表示(这意味着像 $hidden 这样的内容将被尊重)。

它支持将接收到的 ID 转换为模型实例

// received: '3'
public User $user;

将数组/对象转换为未保存的实例

// received: ['name' => 'Try Airwire on a new project', 'priority' => 'highest']
public function addTask(Task $task)
{
    $task->save();
}

将属性/返回值转换为数组

public User $user;
// response: {"name": "John Doe", "email": "john@example.com", ... }

public find(string $id): Response
{
    return User::find($id);
}
// same response as the property

如果您希望对数据编码有更多控制,可以按属性逐个添加 Decoded 属性。这对于返回模型 id 很有用,即使属性持有其实例也是如此

#[Wired] #[Encode(method: 'getKey')]
public User $user; // returns '3'

#[Wired] #[Encode(property: 'slug')]
public Post $post; // returns 'introducing-airwire'

#[Wired] #[Encode(function: 'generateHashid')]
public Post $post; // returns the value of generateHashid($post)

默认值

您可以为无法在类中直接指定的属性指定默认值

#[Wired(default: [])]
public Collection $results;

这些值将包含在生成的JS文件中,这意味着组件即使仅在前端初始化,在没有向服务器发送任何请求的情况下,也将具有正确的初始状态。

只读值

属性也可以是只读的。这告诉前端不要在请求数据中发送它们。

只读属性的典型用例是仅由服务器写入的数据,例如查询结果

// Search/Filter component

#[Wired(readonly: true, default: [])]
public Collection $results;

组件挂载

组件可以有一个mount()方法,它返回初始状态。这种状态在组件在前端实例化时不可访问(与属性的默认值不同),因此组件会从服务器请求数据。

mount()的一个良好用例是