块级格式化上下文BFC
BFC是block formatting context,在文档流的类型中,普通文档流属于FC,而BFC可以理解为一种占位的普通文档流。它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用。
-----------------------------------------------------------------
传统的写法
首先我们回顾下require.js的写法。假设我们有两个js文件: index.js
和content.js
,现在我们想要在index.js
中使用content.js
返回的结果,我们要怎么做呢?
//首先定义: //content.js define('content.js', function(){ return 'A cat'; }) //然后require: //index.js require(['./content.js'], function(animal){ console.log(animal); //A cat })
那CommonJS是怎么写的呢?
//index.js var animal = require('./content.js') //content.js module.exports = 'A cat'
ES6的写法
//index.js import animal from './content' //content.js export default 'A cat'
---------------------------------------------------------------------------------------------------------------------------------------------------------------
//content.js export default 'A cat' export function say(){ return 'Hello!' } export const type = 'dog'
上面可以看出,export命令除了输出变量,还可以输出函数,甚至是类(react的模块基本都是输出类)
//index.js import { say, type } from './content' let says = say() console.log(`The ${type} says ${says}`) //The dog says Hello
这里输入的时候要注意:大括号里面的变量名,必须与被导入模块(content.js)对外接口的名称相同。
如果还希望输入content.js中输出的默认值(default), 可以写在大括号外面。
//index.js import animal, { say, type } from './content' let says = say() console.log(`The ${type} says ${says} to ${animal}`) //The dog says Hello to A cat
修改变量名
此时我们不喜欢type这个变量名,因为它有可能重名,所以我们需要修改一下它的变量名。在es6中可以用as
实现一键换名。
//index.js import animal, { say, type as animalType } from './content' let says = say() console.log(`The ${animalType} says ${says} to ${animal}`) //The dog says Hello to A cat
--------------------------------------------------------------------------------------------------------------------------
function view() { return <ul id="filmList" className="list"> <li className="main">Detective Chinatown Vol 2</li> <li>Ferdinand</li> <li>Paddington 2</li> </ul> } // 接下来,我们要将JSX编译成js, 也就是hyperscript。我们先用Babel编译一下,看这段JSX转成js会是什么样子,打开命令行,输入npm run compile,得到的compile.js: function view() { return h( "ul", { id: "filmList", className: "list" }, h( "li", { className: "main" }, "Detective Chinatown Vol 2" ), h( "li", null, "Ferdinand" ), h( "li", null, "Paddington 2" ) ); }
可以看出h
函数接收的参数,第一个参数是node的类型,比如ul
,li
,第二个参数是node的属性,之后的参数是node的children,假如child又是一个node的话,就会继续调用h
函数。
清楚了Babel会将我们的JSX编译成什么样子后,接下来我们就可以继续在index.js中来写h
函数了。
function flatten(arr) { return [].concat(...arr) } function h(type, props, ...children) { return { type, props: props || {}, children: flatten(children) } }
我们的h
函数主要的工作就是返回我们真正需要的hyperscript对象,只有三个参数,第一个参数是节点类型,第二个参数是属性对象,第三个是子节点的数组。
flatten(children)
这个操作是因为children这个数组里的元素有可能也是个数组,那样就成了一个二维数组,所以我们需要将数组拍平成一维数组。[].concat(...arr)
是ES6写法,传统的写法是[].concat.apply([], arr)
我们现在可以先来看一下h
函数最终返回的对象长什么样子。
function render() { console.log(view()) } // 下面我们就可以根据VDOM, 来渲染真实DOM了。先改写render函数: function render(el) { el.appendChild(createElement(view(0))) }
function createElement(node) { if (typeof(node) === 'string') { return document.createTextNode(node) } let { type, props, children } = node const el = document.createElement(type) setProps(el, props) children.map(createElement) .forEach(el.appendChild.bind(el)) return el } function setProp(target, name, value) { if (name === 'className') { return target.setAttribute('class', value) } target.setAttribute(name, value) } function setProps(target, props) { Object.keys(props).forEach(key => { setProp(target, key, props[key]) }) }