对 WebSocket 的理解
WebSocket是HTML5提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。
WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。
WebSocket原理:客户端向 WebSocket 服务器通知(notify)一个带有所有接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。
WebSocket 特点的如下:
- 支持双向通信,实时性更强
- 可以发送文本,也可以发送二进制数据‘’
- 建立在TCP协议之上,服务端的实现比较容易
- 数据格式比较轻量,性能开销小,通信高效
- 没有同源限制,客户端可以与任意服务器通信
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
Websocket的使用方法如下:
在客户端中:
// 在index.html中直接写WebSocket,设置服务端的端口号为 9999
let ws = new WebSocket('ws://localhost:9999');
// 在客户端与服务端建立连接后触发
ws.onopen = function() {
console.log("Connection open.");
ws.send('hello');
};
// 在服务端给客户端发来消息的时候触发
ws.onmessage = function(res) {
console.log(res); // 打印的是MessageEvent对象
console.log(res.data); // 打印的是收到的消息
};
// 在客户端与服务端建立关闭后触发
ws.onclose = function(evt) {
console.log("Connection closed.");
};
JSX语法糖本质
JSX是语法糖,通过babel转成
React.createElement
函数,在babel官网上可以在线把JSX转成React的JS语法
- 首先解析出来的话,就是一个
createElement
函数 - 然后这个函数执行完后,会返回一个
vnode
- 通过vdom的patch或者是其他的一个方法,最后渲染一个页面
script标签中不添加
text/babel
解析jsx语法的情况下
<script>
const ele = React.createElement("h2", null, "Hello React!");
ReactDOM.render(ele, document.getElementById("app"));
</script>
JSX的本质是React.createElement()函数
createElement
函数返回的对象是ReactEelement
对象。
createElement
的写法如下
class App extends React.Component {
constructor() {
super()
this.state = {
}
}
render() {
return React.createElement("div", null,
/*第一个子元素,header*/
React.createElement("div", {
className: "header" },
React.createElement("h1", {
title: "\u6807\u9898" }, "\u6211\u662F\u6807\u9898")
),
/*第二个子元素,content*/
React.createElement("div", {
className: "content" },
React.createElement("h2", null, "\u6211\u662F\u9875\u9762\u7684\u5185\u5BB9"),
React.createElement("button", null, "\u6309\u94AE"),
React.createElement("button", null, "+1"),
React.createElement("a", {
href: "http://www.baidu.com" },
"\u767E\u5EA6\u4E00\u4E0B")
),
/*第三个子元素,footer*/
React.createElement("div", {
className: "footer" },
React.createElement("p", null, "\u6211\u662F\u5C3E\u90E8\u7684\u5185\u5BB9")
)
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
实际开发中不会使用createElement
来创建ReactElement
的,一般都是使用JSX的形式开发。
ReactElement
在程序中打印一下
render() {
let ele = (
<div>
<div className="header">
<h1 title="标题">我是标题</h1>
</div>
<div className="content">
<h2>我是页面的内容</h2>
<button>按钮</button>
<button>+1</button>
<a href="http://www.baidu.com">百度一下</a>
</div>
<div className="footer">
<p>我是尾部的内容</p>
</div>
</div>
)
console.log(ele);
return ele;
}
react通过babel把JSX转成
createElement
函数,生成ReactElement
对象,然后通过ReactDOM.render函
数把ReactElement
渲染成真实的DOM
元素
为什么 React 使用 JSX
- 在回答问题之前,我首先解释下什么是 JSX 吧。JSX 是一个
JavaScript
的语法扩展,结构类似 XML。 - JSX 主要用于声明
React
元素,但 React 中并不强制使用JSX
。即使使用了JSX
,也会在构建过程中,通过 Babel 插件编译为React.createElement
。所以 JSX 更像是React.createElement
的一种语法糖 - 接下来与 JSX 以外的三种技术方案进行对比
- 首先是模板,React 团队认为模板不应该是开发过程中的关注点,因为引入了模板语法、模板指令等概念,是一种不佳的实现方案
- 其次是模板字符串,模板字符串编写的结构会造成多次内部嵌套,使整个结构变得复杂,并且优化代码提示也会变得困难重重
- 所以 React 最后选用了 JSX,因为 JSX 与其设计思想贴合,不需要引入过多新的概念,对编辑器的代码提示也极为友好。
Babel 插件如何实现 JSX 到 JS 的编译? 在 React 面试中,这个问题很容易被追问,也经常被要求手写。
它的实现原理是这样的。Babel 读取代码并解析,生成 AST,再将 AST 传入插件层进行转换,在转换时就可以将 JSX 的结构转换为 React.createElement
的函数。如下代码所示:
module.exports = function (babel) {
var t = babel.types;
return {
name: "custom-jsx-plugin",
visitor: {
JSXElement(path) {
var openingElement = path.node.openingElement;
var tagName = openingElement.name.name;
var args = [];
args.push(t.stringLiteral(tagName));
var attribs = t.nullLiteral();
args.push(attribs);
var reactIdentifier = t.identifier("React"); //object
var createElementIdentifier = t.identifier("createElement");
var callee = t.memberExpression(reactIdentifier, createElementIdentifier)
var callExpression = t.callExpression(callee, args);
callExpression.arguments = callExpression.arguments.concat(path.node.children);
path.replaceWith(callExpression, path.node);
},
},
};
};
React.createElement源码分析
/**
101. React的创建元素方法
*/
export function createElement(type, config, children) {
// propName 变量用于储存后面需要用到的元素属性
let propName;
// props 变量用于储存元素属性的键值对集合
const props = {
};
// key、ref、self、source 均为 React 元素的属性,此处不必深究
let key = null;
let ref = null;
let self = null;
let source = null;
// config 对象中存储的是元素的属性
if (config != null) {
// 进来之后做的第一件事,是依次对 ref、key、self 和 source 属性赋值
if (hasValidRef(config)) {
ref = config.ref;
}
// 此处将 key 值字符串化
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 接着就是要把 config 里面的属性都一个一个挪到 props 这个之前声明好的对象里面
for (propName in config) {
if (
// 筛选出可以提进 props 对象里的属性
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// childrenLength 指的是当前元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度
const childrenLength = arguments.length - 2;
// 如果抛去type和config,就只剩下一个参数,一般意味着文本节点出现了
if (childrenLength === 1) {
// 直接把这个参数的值赋给props.children
props.children = children;
// 处理嵌套多个子元素的情况
} else if (childrenLength > 1) {
// 声明一个子元素数组
const childArray = Array(childrenLength);
// 把子元素推进数组里
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
// 最后把这个数组赋值给props.children
props.children = childArray;
}
// 处理 defaultProps
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 最后返回一个调用ReactElement执行方法,并传入刚才处理过的参数
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
入参解读:创造一个元素需要知道哪些信息
export function createElement(type, config, children)
createElement 有 3 个入参,这 3 个入参囊括了 React 创建一个元素所需要知道的全部信息。
type
:用于标识节点的类型。它可以是类似“h1”“div”这样的标准 HTML 标签字符串,也可以是 React 组件类型或React fragment
类型。config
:以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中。children
:以对象形式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的“子节点”“子元素”
React.createElement("ul", {
// 传入属性键值对
className: "list"
// 从第三个入参开始往后,传入的参数都是 children
}, React.createElement("li", {
key: "1"
},