Render渲染函数和JSX

Posted 琪琪的园子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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>

以上是关于Render渲染函数和JSX的主要内容,如果未能解决你的问题,请参考以下文章

虚拟DOM 和 Diff 算法,key的作用,jsx,render函数

vue-12-渲染函数 & JSX

在渲染函数中调用返回 JSX 元素的函数

ElementUI中table使用render/jsx渲染

render()--组件--纯函数

前端:react生命周期