Odoo 14全新前端框架 OWL

Web Library (OWL)是一个小型UI框架(gzip 压缩后不足20kb),用于作为 Odoo 网页客户端的基石。Owl一个使用Typescript编写的现代框架,以简单、连贯的方式吸取了React和Vue最佳编程思想。Owl的主要特性有:

·       声明式的组件系统

·       基于钩子(hook)的响应式系统

·       默认并发模式

·       存储和前台路由

      Owl组件通过ES6类进行定义,使用QWeb模板,底层为虚拟DOM,完美集成钩子并且渲染是异步的。

Owl组件
特性展示

图片

01
如何开发OWL

      Odoo为何决定再开发一套框架呢?这个问题的答案可以有很长。但简而言之,我们相邻当前流行框架非常优秀,但对我们的使用场景并不够优化,还有改进的空间。

如果想了解它与 React 或 Vue 的对比可访问下面网址:

https://github.com/odoo/owl/blob/master/doc/miscellaneous/comparison.md

02
示例

      以下是描述交互组件的简短示例:

const { Component, useState } = owl;

const { xml } = owl.tags;

 

class Counter extends Component {

  static template = xml`

    

      Click Me! []

    `;

  state = useState({ value: 0 });

}

class App extends Component {

  static template = xml`

    

      Hello Owl

      

    `;

  static components = { Counter };

}

const app = new App();

app.mount(document.body);


      注意 counter 组件通过 useState 钩子变为响应式。同时,此处所有示例使用了 xml 帮助标签 来定义行内模板。但这并非强制,很多应用会单独加载模板。

03
设计原则 

      OWL设计用于高度动态的应用,其中修改的需求很普遍并且代码由大型团队维护。

·       基于XML:模板基于XML格式,这可创建出有趣的应用。例如,可以在数据库中进行存储并使用xpath动态修改。

·       浏览器中进行模板编译:这可能并不适合所有应用,但如果需要在浏览器中动态生成用户界面,就非常强大了。例如,通用的表单视图组件可以通过一个XML视图对每个模型生成具体的表单用户界面。

·       不依赖于工具链:对一些应用这极其有用,如出于某些原因(安全/部署/动态模块/具体资源工具),不能够使用基于npm的标准web工具。

Owl的设计初衷并不是快速、小巧(虽然在这两方面它都做得不错)。它不是一个构建应用的随意框架。只有一种定义组件的方式(借助于类)。也没有黑魔法。It just works.

04
快速总览

      应用中的Owl组件用于定义一个(动态)组件树。

        Root

        /   \

       A     B

      / \

     C   D

      状态(state):各组件可管理其自身的本地状态。这是一个简单的ES6类,没有特殊规则:

class Counter extends Component {

  static template = xml`

    

      Click Me! []

    `;

 

  state = { value: 0 };

 

  increment() {

    this.state.value++;

    this.render();

  }

}

      以上示例展示了一个具有本地状态的组件。注意state对象并没有什么超能力,因此在其更新时我们需要手动地调用render函数。这很容易让人觉得麻烦(多次进行时会显得低效)。有一种更好的方式:使用useState钩子,它将对象转化为其自身的响应式(reactive)版本:

const { useState } = owl.hooks;

 

class Counter extends Component {

  static template = xml`

    

      Click Me! []

    `;

 

  state = useState({ value: 0 });

 

  increment() {

    this.state.value++;

  }

}

      注意还可以使用行内语句来替换掉t-on-click这一handler:

      属性(props):子组件经常需要从父组件获取一些信息。这可通过向模板添加所需信息来实现。然后通过props对象中的子组件进行获取。注意有一条重要规则:props对象中包含的信息不由子组件持有,不应进行修改。

class Child extends Component {

  static template = xml`

Hello

`;


}

 

class Parent extends Component {

  static template = xml`

    

        

        

    `;

  static components = { Child };

}

     通信:有很多种方式在组件间进行信息通讯。但最重要的两种方式如下:

·       使用props从父组件向子组件通信,

·       通过触发事件从子组件向父组件通信

      以下的示例描述了这两种机制:

class OrderLine extends Component {

  static template = xml`

    

        


        

Quantity:


    `;

 

  add() {

    this.trigger("add-to-order", { line: props.line });

  }

}

 

class Parent extends Component {

  static template = xml`

    

        <OrderLine

            t-foreach="orders"

            t-as="line"

            line="line" />

    `;

  static components = { OrderLine };

  orders = useState([

    { id: 1, name: "Coffee", quantity: 0 },

    { id: 2, name: "Tea", quantity: 0 },

  ]);

 

  addToOrder(event) {

    const line = event.detail.line;

    line.quantity++;

  }

}

      本例中,OrderLine组件触发了一个add-to-order事件。这会生成一个与DOM树一起冒泡的DOM事件。然后由父组件拦截,接着(从detail键)获取该行并增加其数量。参见事件处理一页获取事件运行的更多详情。

      注意在OrderLine组件直接修改line对象时也会有效。但这不是一种好的做法:它有效是因为从子组件所接收到的props是响应式的,因此子组件会与父组件的实现之间进行配对。