zoukankan      html  css  js  c++  java
  • Render渲染函数和JSX

    1.Render函数:render是用来替换temlate的,需要更灵活的模板的写法的时候,用render。

    官网API地址:https://cn.vuejs.org/v2/guide/render-function.html

    通常写的h为createElement的缩写,createElement 会返回虚拟节点 (virtual node)”,也常简写它为“VNode,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。

    • 第一个参数为{String | Object | Function}是一个 HTML 标签名、组件选项对象,为必选项。
    • 第二个参数为 {Object},是一个与模板中属性对应的数据对象。可选。
    • 第三个参数为{String | Array},是子级虚拟节点 (VNodes),由 `createElement()` 构建而成,也可以使用字符串来生成“文本虚拟节点”。可选。

    1.1一个最简单的render函数例子:

    import Vue from "vue";
    import App from "./App.vue";
    import router from "./router";
    import store from "./store";
    import Bus from "./lib/bus";
    import $ from "jquery";
    
    Vue.config.productionTip = false;
    Vue.prototype.$bus = Bus;
    
    new Vue({
      router,
      store,
      //render: h => h(App)
      render(h) {
        return h('div', {
          attrs: {
            id:"box"
          },
          style: {
            color:"blue"
          }
        },"Caoqi");
      },
    }).$mount("#app");

    1.2当第一个参数为组件时的写法如下,引入的组件为之前博客中讲过的Count-To组件(https://www.cnblogs.com/qicao/p/10805715.html)

    import Vue from "vue";
    import App from "./App.vue";
    import router from "./router";
    import store from "./store";
    import Bus from "./lib/bus";
    import $ from "jquery";
    import CountTo from "_c/count-to";
    
    Vue.config.productionTip = false;
    Vue.prototype.$bus = Bus;
    
    new Vue({
      router,
      store,
      render: function(h) {
        return h(CountTo, {
          /**
           * class作为一个保留字必须用引号包裹
           * 接受一个字符串、对象或字符串和对象组成的数组
           */
          // 'class':'count-up wrapper',
          //class: ["count-to", true ? "classA" : "classB"],
          class: { "count-to": 1 === 1 },
          // 组件 prop
          props: { endVal: 200 },
          // DOM 属性
          domProps: {
            //innerHTML: "baz"
          },
          /**
           * 事件监听器在 `on` 属性内,
           * 但不再支持如 `v-on:keyup.enter` 这样的修饰器。需要在处理函数中手动检查 keyCode。
           */
          on: {
            "on-animation-end": function(val) {
              console.log("animation end");
            }
          },
          /**
           * 仅用于组件,用于监听原生事件,而不是组件内部使用`vm.$emit` 触发的事件。
           */
          nativeOn: {
            click: () => console.log("I am clicked!")
          },
          /*自定义指令*/
          directives: [],
          // 如果组件是其它组件的子组件,需为插槽指定名称
          slot: "name-of-slot"
        });
      }
    }).$mount("#app");

    显示效果:

    1.3 创建子级虚拟节点:

    import Vue from "vue";
    import App from "./App.vue";
    import router from "./router";
    import store from "./store";
    import Bus from "./lib/bus";
    import $ from "jquery";
    import CountTo from "_c/count-to";
    
    Vue.config.productionTip = false;
    Vue.prototype.$bus = Bus;
    
    new Vue({
      router,
      store,
      /**
       * @param {String | Array} h 
       * 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,也可以使用字符串来生成“文本虚拟节点”。可选。
       */
      //render:h=>h('div','123')
      render:function(h) {
        return h('div', [
          h('span','span1'),  
          h('span','span2'),
        ])
      }
    }).$mount("#app");

     1.4使用 JavaScript 代替模板功能

    模板代码:

    <template>
      <div>
        <ul @click="handlerClick">
          <li
            @click.stop="handlerClick"
            v-for="(item,index) in list"
            :key="`list_item_${index}`"
          >{{item.name}}</li>
        </ul>
      </div>
    </template>
    <script>
    export default {
      data() {
        return {
          list: [
            {
              name: "张三"
            },
            {
              name: "李四"
            }
          ]
        };
      },
      methods: {
        handlerClick: function(event) {
          console.log(event);
        }
      }
    };
    </script>

    main.js: 

    import Vue from "vue";
    import App from "./App.vue";
    import router from "./router";
    import store from "./store";
    import Bus from "./lib/bus";
    import $ from "jquery";
    import CountTo from "_c/count-to";
    
    Vue.config.productionTip = false;
    Vue.prototype.$bus = Bus;
    
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount("#app");

     

    若使用render函数中的js代替模板:只要在原生的 JavaScript 中可以轻松完成的操作,Vue 的渲染函数就不会提供专有的替代方法。比如,在模板中使用的 v-if 和 v-for需要用JavaScript 的 if/else 和 map 来重写:

    import Vue from "vue";
    import App from "./App.vue";
    import router from "./router";
    import store from "./store";
    import Bus from "./lib/bus";
    import $ from "jquery";
    import CountTo from "_c/count-to";
    
    Vue.config.productionTip = false;
    Vue.prototype.$bus = Bus;
    
    const handleClick = event => {
      console.log(event);
      event.stopPropagation();
    };
    
    let list = [{ name: "张三" }, { name: "李四" }];
    /**
     * Array map用法:
     * 功能:将原数组映射成新数组
     * https://www.zhangxinxu.com/wordpress/2013/04/es5%E6%96%B0%E5%A2%9E%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95/#map
     */
    const getLiEleArr = h => {
      return list.map((item,index) =>
        h(
          "li",
          {
            on: {
              click: handleClick
            },
            key:`list_item_${index}`
          },
          item.name
        )
      );
    };
    /**等效于 */
    /*function getLiEleArr(h) {
      return list.map(function(item, index) {
        return h(
          "li",
          {
            on: {
              click: handleClick
            },
            key: `list_item_${index}`
          },
          item.name
        );
      });
    }*/
    
    new Vue({
      router,
      store,
      render: function(h) {
        return h(
          "ul",
          {
            on: {
              click: handleClick
            }
          },
          getLiEleArr(h)
        );
      }
    }).$mount("#app");

    2.函数式组件:函数组件可以用render方式,可以用模板方式。函数组件主要用来做组件的外壳,也就是写模板之前,可以先对传进来的上下文做一些处理。这个“壳”的作用有点类似模板语法里的<template>标签,本身不会渲染,只是做包裹。

    官方API地址:https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6

    我们可以把函数式组件想像成组件里的一个函数,输入参数是渲染上下文(render context),返回值是渲染好的HTML

    对于函数式组件,可以这样定义:

    • Stateless(无状态):组件自身是没有状态的
    • Instanceless(无实例):组件自身没有实例,也就是没有this,事件只能由父组件传递

    例子:

    <div id="app">
        <smart-list :items=items></smart-list>
    </div>

    app.vue:

       //当父组件传过来的是空items时
        var EmptyList = {template: '<p>Empty list</p>'};
        //当父组件传来的items元素为对象类型时
        var TableList = 'ul'
        // 当父组件定义了isOrdered变量且为true
        var UnorderedList = 'ul'
     
        //定义组件
        Vue.component('smart-list', {
            //标记为函数式组件
            functional: true,
     
            //render函数
            render: function (createElement, context) {
                // console.log(context)//若不理解可以打印出来context来看看里面都有些什么东西
     
                //规定组件的渲染规则
                function appropriateListComp() {
                    //获取父组件传来的数据
                    var items = context.props.items;
     
                    //若空,则返回前面定义的emptylist
                    if (items.length === 0) return EmptyList;
                    //若为对象
                    if (typeof items[0] === 'object') return TableList;
                    //其他
                    return UnorderedList
                }
     
                //生成模板
                return createElement(
                    //模板标记为渲染规则函数返回值
                    appropriateListComp(),
                    //模板子元素,返回一个数组
                    Array.apply(null, {length: context.props.items.length}).map(function (value, index) {
                        return createElement('li',context.props.items[index].name)
                    })
                )
            },
            props: {
                items: {
                    type: Array,
                    required: true
                },
                isOrdered: Boolean
            }
        });
        new Vue({
            el: '#app',
            data:{
                items:[
                    {
                        name:'a',
                        id:0
                    },
                    {
                        name:'b',
                        id:1
                    },
                    {
                        name:'c',
                        id:2
                    }
                ]
            }
        })

    最终浏览器渲染结果:

    <div id="app">
        <ul>
            <li>a</li>
            <li>b</li>
            <li>c</li>
        </ul>
    </div>

     再举一个例子,代码目录结构如下:

    render-page:

    <template>
      <div>
        <list :list="list" :render="renderFunc"></list>
      </div>
    </template>
    <script>
    import List from "_c/list";
    export default {
      data() {
        return {
          list: [
            {
              name: "张三"
            },
            {
              name: "李四"
            }
          ]
        };
      },
      components: {
        List
      },
      methods: {
        renderFunc: function(h, name) {
          return h("i", {
            style: {
              color: "pink"
            }
          },name);
        }
      }
    };
    </script>

    router.js:

    import Home from "@/views/Home.vue";
    
    export default [
      {
        path: "/",
        alias: "/home_page",
        name: "home", //加上name属性  命名路由
        component: Home,
        props: route => ({
          food: route.query.food
        }),
        beforeEnter: (to, from, next) => {
          // if (from.name === "about") alert("这是从about来的");
          // else alert("这不是从about来的");
          next();
        }
      },
      {
        path: '/render_page',
        name: 'render_page',
        component: () => import('@/views/render-page.vue')
      }
      {
        path: "*",
        component: () => import("@/views/error_404.vue")
      }
    ];

     list/index.js:

    import List from "./list.vue";
    export default List;

     list/list.vue:

    <template>
      <ul>
        <li v-for="(item,index) in list" :key="`item_${index}`">
          <span v-if="!render">{{item.name}}</span>
          <render-dom v-else :render-func="render" :name="item.name"></render-dom>
        </li>
      </ul>
    </template>
    <script>
    import RenderDom from "_c/render-dom";
    export default {
      name: "List",
      components: {
        RenderDom
      },
      props: {
        list: {
          type: Array,
          default: () => []
        },
        render: {
          type: Function,
          default: () => {}
        }
      }
    };
    </script>

    render-dom.js:

    export default {
      functional: true,
      // Props 是可选的
      props: {
        name: String,
        renderFunc: Function
      },
      // 为了弥补缺少的实例,提供第二个参数作为上下文
      render: (h, ctx) => {
        return ctx.props.renderFunc(h, ctx.props.name);
      }
    };

     运行效果:

    JSX(可参考https://juejin.im/post/5affa64df265da0b93488fdd):

    什么是JSX:

    JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。

    将刚才的例子中的render-page.vue中的render函数改为jsx语法(它可以让我们回到更接近于模板的语法上):

    <template>
      <div>
        <list :list="list" :render="render"></list>
      </div>
    </template>
    <script>
    import List from "_c/list";
    export default {
      data() {
        return {
          list: [
            {
              name: "张三"
            },
            {
              name: "李四"
            }
          ]
        };
      },
      components: {
        List
      },
      methods: {
        render: function(h, name) {
          // return h("i", {
          //   style: {
          //     color: "pink"
          //   }
          // },name);
          return (
            ////绑定事件需要用on前缀
            <i on-click={this.handlerClick} style={{color:'pink'}}>{name}</i>
        );
        },
        handlerClick(event){
          console.log(event);
        }
      }
    };
    </script>

    在JSX中使用组件(使用和上一个例子同样的目录结构):

    注意在render或者JSX中写的组件是不需要在components中注册的。

    render-page.vue:

    <template>
      <div>
        <list :list="list" :render="render"></list>
      </div>
    </template>
    <script>
    import List from "_c/list";
    import CountTo from "_c/count-to";
    export default {
      data() {
        return {
          list: [
            {
              number: 100
            },
            {
              number: 200
            }
          ]
        };
      },
      components: {
        List
      },
      methods: {
        render: function(h, number) {
          return (
            //绑定事件需要用on-开头 绑定原生事件用nativeOn-开头
            <CountTo nativeOn-click={this.nativeHandlerOn} on-on-animation-end={this.handlerEnd} endVal={number} />
        );
        },
        handlerClick(event) {
          console.log(event);
        },
        handlerEnd() {
          console.log("End!!!");
        },
        nativeHandlerOn(){
          console.log("这是原生事件");
        }
      }
    };
    </script>

    list.vue:

    <template>
      <ul>
        <li v-for="(item,index) in list" :key="`item_${index}`">
          <span v-if="!render">{{item.number}}</span>
          <render-dom v-else :render-func="render" :number="item.number"></render-dom>
        </li>
      </ul>
    </template>
    <script>
    import RenderDom from "_c/render-dom";
    export default {
      name: "List",
      components: {
        RenderDom
      },
      props: {
        list: {
          type: Array,
          default: () => []
        },
        render: {
          type: Function,
          default: () => {}
        }
      }
    };
    </script>

    render-dom.js:

    export default {
      functional: true,
      // Props 是可选的
      props: {
        number: Number,
        renderFunc: Function
      },
      // 为了弥补缺少的实例,提供第二个参数作为上下文
      render: (h, ctx) => {
        return ctx.props.renderFunc(h, ctx.props.number);
      }
    };

    效果图:

     

    作用域插槽

            作用域插槽就是父组件在调用子组件的时候给子组件传了一个插槽,这个插槽为作用域插槽,该插槽必须放在template标签里面,同时声明从子组件接收的数据放在一个自定义属性内,并定义该数据的渲染方式。适合的场景是至少包含三级以上的组件层级,是一种优秀的组件化方案!

    render-page.vue:

    <template>
      <div>
        <list :list="list">
          <count-to slot-scope="count" :endVal="count.number"></count-to>
        </list>
      </div>
    </template>
    <script>
    import List from "_c/list";
    import CountTo from "_c/count-to";
    export default {
      data() {
        return {
          list: [
            {
              number: 100
            },
            {
              number: 200
            }
          ]
        };
      },
      components: {
        List,
        CountTo
      },
      methods: {
      }
    };
    </script>

    list.vue:

    <template>
      <ul>
        <li v-for="(item,index) in list" :key="`item_${index}`">
          <!-- <span v-if="!render">{{item.number}}</span>
          <render-dom v-else :render-func="render" :number="item.number"></render-dom> -->
          <slot :number="item.number"></slot>
        </li>
      </ul>
    </template>
    <script>
    import RenderDom from "_c/render-dom";
    export default {
      name: "List",
      components: {
        RenderDom
      },
      props: {
        list: {
          type: Array,
          default: () => []
        },
        render: {
          type: Function,
          default: () => {}
        }
      }
    };
    </script>
  • 相关阅读:
    gulp的使用
    Js中call(),apply()的使用
    HTML中<meta>标签的使用
    字符串及数组常用方法
    css—transform
    《Java设计模式》——适配器模式
    全文检索工具包Lucene以及企业及应用Solr的学习(二)—— solr中edismax用到的Query Function以及java扩展
    全文检索工具包Lucene以及企业及应用Solr的学习(一)
    最近发现服务器发生了一些问题
    TxT读取写入
  • 原文地址:https://www.cnblogs.com/qicao/p/10822811.html
Copyright © 2011-2022 走看看