1. React 长什么样

React 是 facebook 开源出来的一套前端方案,官网在 https://reactjs.org 。

先看一个简单的样子:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</head>
<body>
<h1>Hello</h1>
<div id="place"></div>
<script type="text/javascript">
var attribute = {
className: 'text',
style: {color: 'red'},
id: 'me',
href: 'http://' + 's.zys.me'
};
var inner = React.createElement('a', attribute, 'React');
var instance = React.createElement('h1', null, '字符串在中间', inner);
ReactDOM.render(instance, $('#place')[0]);
</script>
</body>
</html>

从上面可以看出,它的使用其实跟其它很多框架的方式差不多,即,找到一个 DOM 节点,然后在这个节点上做事,类似 jQuery 的 $(dom).xxx({}) 。

在 React.createElement 中,第一个参数是标签名,第二个是 config ,其中包括了节点属性(class 因为关键词问题改用 className) ,第三个及以后,是子级节点,或者说子级元素。

上面两个 createElement 得到的最终结果,是:

<h1>
字符串在中间
<a class="text" style="color: red" id="me" href="http://s.zys.me">React</a>
</h1>

当然,这里的重点,不是 React.createElement ,而是它返回的那个 instance ,事实上,从源码 https://unpkg.com/react@16.2.0/umd/react.development.js 中看的话,可以看出它返回的是一个 ReactElement ,这个 ReactElement 就是整个 React 体系于众不同的地方,否则它直接返回一个 DOM Element 就好了。

最后一行的 ReactDOM.render 就是处理 ReactElement 的, ReactDOM 的源码在 https://unpkg.com/react-dom@16.2.0/umd/react-dom.development.js , 这个东西自己实现了一套挂节点的树结构(所谓的 Virtual DOM),中间隔了一层再去处理真实的 DOM 渲染。这么做的好处是,因为自己有一套完整的树结构的,所以,可以在上面实现很多在原始 DOM 结构上不能做,或者不方便做的事。比如,异构渲染之类的。当然,局限的地方也很明显,本质上还是“节点”,其实没有一个往上的抽象,暴露的细节还是很多的,这种情况下,东西可以做得比较细,但是付出的成本也比较大,我是这样预测的。

2. ReactElement 的定义

最开始,已经简单演示了针对像 a h1 这类原生 DOM 元素的 ReactElement 定义, 自然,“能接受一个确定值的地方也应该可以接受一个函数”是 js 的一个惯例吧:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
</head>
<body>
<div></div>
<script type="text/javascript">
function Component(props) {
if(props.tag === 'h1') {
return React.createElement('h1', null, '大标题');
} else {
return React.createElement('span', null, '普通文本');
}
}
var instance = React.createElement(Component, {tag: 'h1'});
ReactDOM.render(instance, $('div')[0]);
</script>
</body>
</html>

上面代码的过程,大概是:

  • React.createElement(type, config, children) 返回 ReactElement() 。
  • ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props) ,上面 config 的一些东西,会补为这里的几个参数,及 props 里的东西。
  • ReactElement() 的调用会返回一个 element ,这是一个比较单纯的结构体,里面会有 typekey ref props _owner $$typeof 等内容。
  • ReactDOM.render(element, container, callback) 是对 renderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) 的调用, element 也转为 children 的统一概念了。
  • 然后是到 DOMRender (从 reactReconciler 来) 的 updateContainer(element, container, parentComponent, callback) ,最外层的调用, container 就是一个 newRoot ,前面的 children概念这里又变成 element 了。
  • 上面会处理一下 container ,接着到 scheduleTopLevelUpdate(current, element, callback) , current 是从 container 里取的。
  • scheduleTopLevelUpdate 是会作 scheduleWork(current, expirationTime) ,应该是处理一些同步的事。还有 构造一个 update 的结构体,然后 insertUpdateIntoFiber(current, update) , element被放到 update 中的 partialState: { element: element } 。
  • insertUpdateIntoFiber(fiber, update) , 新的 element 在 update 中,而 fiber 是 container的范畴。这里只是处理“两个队列”的状态,干活的还是在 scheduleWork() 中。
  • 这个地方开始, current 和 element 就分开了,current 继续进入 scheduleWork(fiber, expirationTime) 进行下一步处理。而 element 只在 insertUpdateIntoFiber 中更新于队列状态。
  • scheduleWork() 里实际上会用 scheduleWorkImpl(fiber, expirationTime, isErrorRecovery) ,这里开始迭代 fiber ,离真实的节点创建还有一半的路要走吧,中间又各种处理,最后 createInstance() 会创建真实的节点。

反正,我从源码中找了一长串,还是没搞明白 createElement() 第一个参数的 type 可以是,应该是一个什么东西。

从官方的文档来看 https://reactjs.org/docs/react-api.html#createelement ,这个 type 可以是三类东西:

  • 原生类节点,的标签字符串,比如 div , span 。
  • React component , React 组件。
  • React fragment ,这好像是 v16.2.0 加入的新机制,简单来说它使 React 可以支持直接渲染“一串”节点,而之前,只能直接渲染“一个”节点,如果是一串的需求,那么在外面需要包一个节点。我理解大概就是下面这个样子吧:
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</head>
<body>
<div></div>
<script type="text/javascript">
function Component(props) {
return ['1', React.createElement('h1', null, props.text)];
}
var instance = React.createElement(Component, {text: '啊啊啊'});
ReactDOM.render(instance, $('div')[0]);
</script>
</body>
</html>

React 组件有两种类型,一种是简单的“函数式组件”,另一种是复杂点的“类组件”。

2.1. 函数式组件

函数式组件就是接受 props ,然后返回一个 React element :

function Component(props) {
return React.createElement('h1', null, props.text);
}

2.2. 类组件

类组件,是继承 React.Component ,然后重写一些方法:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16.2.0/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16.2.0/umd/react-dom.development.js"></script>
</head>
<body>
<div></div>
<script type="text/javascript">
class MyComponent extends React.Component {
render(){
return React.createElement('h1', null, this.props.text);
}
}
var instance = React.createElement(MyComponent, {text: '哈哈'});
ReactDOM.render(instance, $('div')[0]);
</script>
</body>
</html>

3. createElement 的 DSL 方案 JSX

前面提过,如果你想通过 createElement 构造一个类似:

<h1>
字符串在中间
<a class="text" style="color: red" id="me" href="http://s.zys.me">React</a>
</h1>

那么你需要的 js 大概是:

var attribute = {
className: 'text',
style: {color: 'red'},
id: 'me',
href: 'http://' + 's.zys.me'
};
var inner = React.createElement('a', attribute, 'React');
var instance = React.createElement('h1', null, '字符串在中间', inner);
ReactDOM.render(instance, $('#place')[0]);

看起来是比较麻烦的,于 React 中引入了一个新的东西,JSX ,即 JavaScript XML ,简单来说,就是 javascript 和 XML 的混写,上面的代码可以写成:

var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
var instance = <h1>字符串在中间 {inner}</h1>;
ReactDOM.render(instance, $('#place')[0]);

甚至是:

var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
ReactDOM.render(<h1>字符串在中间 {inner}</h1>, $('#place')[0]);

可以看出,比原来直接写 createElement 的方式要简洁得多, JSX 除了支持直接写 XML 风格的标签,还可以使用 {} 处理表达式。不过, JSX 并不是浏览器的标准,要让它跑在浏览器,需要在发布前专门编译到纯 js ,平时测试,也可以直接用 babel 来跑:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.js"></script>
</head>
<body>
<h1>Hello</h1>
<div id="place"></div>
<script type="text/babel">
var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
ReactDOM.render(<h1>字符串在中间 {inner}</h1>, $('#place')[0]);
</script>
</body>
</html>

注意,上在的 script 的 type 写的是 text/babel 哦。

babel 也有命令行的工具,先安装:

npm install -g babel
npm install -g babel-cli
npm install -g babel-preset-react

然后对于一个 demo.jsx :

var inner = <a className="text" style={ {color: 'red'} } id="me" href={'http://' + 's.zys.me'}>React</a>;
ReactDOM.render(<h1>字符串在中间 {inner}</h1>, $('#place')[0]);

可以使用 babel 编译它:

babel --presets=react demo.jsx

就可以看到标准的 js 输出了:

var inner = React.createElement(
"a",
{ className: "text", style: { color: 'red' }, id: "me", href: 'http://' + 's.zys.me' },
"React"
);
ReactDOM.render(React.createElement(
"h1",
null,
"\u5B57\u7B26\u4E32\u5728\u4E2D\u95F4 ",
inner
), $('#place')[0]);

babel 默认输出的到标准输出,可以通过 -o 输出到指定文件。

平时的前端项目,一般把 babel 和 webpack 结合使用,在构建过程处理 JSX 格式。

3.1. JSX 中的名字

JSX 中的名字,如果是自定义组件,名字开头需要大写,小写的是 DOM 标签。

名字中,可以带名字空间:

<MyComponent.A.B />

也可以是变量值:

let My = [MyComponent][0];
return <My />

但是,不能带表达式(下面是错误的):

<[MyComponent][0] />

提示一下, JSX 不是模板,它的行为,更像是“宏”。本质,还是 js 代码。 宏是没有运行时能力的,自然无法求值表达式。

4. 使用 state 处理数据单向绑定

前面已经提到过 props 了, props 一般处理初始化后就不改变的数据,对于一个组件中要变化的数据,需要放在 state 中处理,因为 React 专门设计了组件的 setState() 来维护 state (不要直接更改 state ,而是使用 setState()) ,同时 setState() 还处理节点渲染相关的事,通过函数调用,来实现“数据到展现”的单向绑定。

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.js"></script>
</head>
<body>
<div id="place"></div>
<script type="text/babel"> class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {date: (new Date()).valueOf()};
setInterval(() => this.setState({date: (new Date()).valueOf()}), 1000);
} render(){
return <h1>Hello {this.props.name} {this.state.date}</h1>;
} } ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);
</script>
</body>
</html>

4.1. setState

setState() 也可以接受一个函数,这个函数的参数第一个是当前的 state ,另一个是 props :

class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {date: (new Date()).valueOf()};
setInterval(() => this.setState((prevState, props) => ({date: prevState.date + 1000})), 1000);
} render(){
return <h1>Hello {this.props.name} {this.state.date}</h1>;
} } ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);

5. 事件处理

React 自己维护了一个节点状态的树结构,并且,同时也自己实现了一套事件机制,只是这套事件,跟浏览器自身的区别不是很大:

class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {date: (new Date()).valueOf()};
setInterval(() => this.setState((prevState, props) => ({date: prevState.date + 1000})), 1000);
} handleClick = () => {
console.log(this);
} render(){
return <h1 onClick={this.handleClick}>Hello {this.props.name} {this.state.date}</h1>;
} } ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);

注意那个 handleClick 的写法:

handleClick = () => {
console.log(this);
}

5.1. this 的问题

如果不用“箭头函数”把 this 固定住了,那么在使用时就需要显式绑定:

handlerClick(){
console.log(this);
} render(){
return <h1 onClick={this.handleClick.bind(this)}>Hello {this.props.name} {this.state.date}</h1>;
}

React 自己的事件,是“驼峰式”的名字,比如 onClick ,而 onclick 则是浏览器自己的事件了。

其它支持的完整的事件列表,见: https://reactjs.org/docs/events.html

6. 列表与 key

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/babel-standalone@6.15.0/babel.js"></script>
</head>
<body>
<div id="place"></div>
<script type="text/babel"> class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {itemList: [{name: 'abc', id: 'a'}, {name: 'oiii', id: 'b'}] };
} handleClick(o){
return (e) => {
console.log(o);
}
} render(){
return <div>
{this.state.itemList.map( (o) => (<div key={o.id} onClick={this.handleClick(o)}>{o.name}</div>) ) }
</div>
} } ReactDOM.render(<MyComponent name="哈哈" />, $('#place')[0]);
</script>
</body>
</html>

对于列表的渲染, React 要求节点上需要添加 key 属性,否则,会报 warn (是的,不会报错)。如果没有明确的 id 之类的标识,可以使用序列的索引值 index 。这个 key 属性的作用, React 会用于标识节点,以便在数据变更时,对应地可以追踪到节点的变更情况。

7. 组件的各状态 Hook (生命周期)

对于一个 React 组件,准备说,“类组件”,它在渲染及销毁过程中,每个状态转换时, React 都预留了相关的 Hook 函数,这些函数,一共 10 个,可以分成 4 类:

  • 渲染时

    • constructor(props)
    • componentWillMount()
    • render()
    • componentDidMount() ,从这里开始, setState() 会触发重新的 render() 。
  • 修改时
    • componentWillReceiveProps(nextProps) , props 改变时触发,可以由于父组件的变化引起。 setState() 一般不会触发这个过程。
    • shouldComponentUpdate(nextProps, nextState) ,如果返回 false ,则使 React “尽量” 不要重新渲染组件,注意,只是 “尽量” 而已。如果渲染上有性能问题,则应该考虑其它解决方案。
    • componentWillUpdate(nextProps, nextState) ,上面的 shouldComponentUpdate() 如果返回 false ,则这个过程一定不会触发,现时,第一次初始化不会触发这个过程。
    • render()
    • componentDidUpdate(prevProps, prevState) ,第一次初始化不会触发这个过程。
  • 销毁时
    • componentWillUnmount()
  • 出错时
    • componentDidCatch(error, info)

这几个方法的一些细节:

componentWillReceiveProps

  • 初始化时不会被调用。
  • 可以在这里作 setState() 。
  • props 没变化时也可能被调用。

componentWillUpdate

  • 初始化时不会被调用。
  • 不能在这里作 setState() , 否则会再次触发变化流程,造成死循环。
  • shouldComponentUpdate() 为 false 时不会调用。

componentDidUpdate

  • 初始化时不会被调用。
  • 远程请求常放在这里,因为可以先判断 props 的相应变化后再决定是否作新的远程调用。

除了上面讲的过程 Hook 函数,及前面用过的 setState() ,组件还有一个 forceUpdate(callback)方法,它的调用可以触发 render() ,并且跳过 shouldComponentUpdate() 。

另外,在“类”层面,有两个属性, defaultProps 用于定义默认的 props , displayName ,用于定义调试时显示的名字。

试试各个过程的表现吧:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
</head>
<body>
<div></div>
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {name: '初始化'};
let that = this;
setTimeout(function(){
console.log('2s go ...');
that.setState({name: '修改了的'});
}, 2000);
console.log('constructor');
} componentWillMount(){
console.log('componentWillMount');
} componentDidMount(){
console.log('componentDidMount');
} componentWillReceiveProps(nextProps){
console.log('componentWillReceiveProps');
} shouldComponentUpdate(nextProps, nextState){
console.log('shouldComponentUpdate');
return true;
} componentWillUpdate(nextProps, nextSate){
console.log('componentWillUpdate');
} componentDidUpdate(prevProps, prevState){
console.log('componentDidUpdate');
} componentWillUnmount(){
console.log('componentWillUnmount');
} componentDidCatch(error, info){
console.log('componentDidCatch');
} render(){
console.log('render');
if(this.props.error){throw Error()};
return <h1>{this.state.name}</h1>;
} }
ReactDOM.render(<MyComponent />, $('div')[0]); setTimeout(function(){
console.log('5s go ...');
ReactDOM.render(<MyComponent />, $('div')[0]);
}, 5000); setTimeout(function(){
console.log('10s go ...');
ReactDOM.render(null, $('div')[0]);
}, 10000); </script>
</body>
</html>

上面的代码会输出:

constructor
componentWillMount
render
componentDidMount
2s go ...
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
5s go ...
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
10s go ...
componentWillUnmount

上面过程看起来都很好理解的。

少的那个 componentDidCatch 是用来抓子组件错误的:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
</head>
<body>
<div></div>
<script type="text/babel">
class ErrorWrapper extends React.Component {
constructor(props){
super(props);
this.state = {error: null};
} componentDidCatch(error, info){
console.log('componentDidCatch');
console.log(error, info);
this.setState({error: {error: error, info: info}});
} render(){
if(!!this.state.error) {
return <h1>Error Here</h1>
} else {
return this.props.children || '';
}
}
} class MyComponent extends React.Component {
render(){
throw Error();
}
} ReactDOM.render(<ErrorWrapper><MyComponent /></ErrorWrapper>, $('div')[0]); </script>
</body>
</html>

上面代码占, MyComponent 的错误,会触发 ErrorWrapper 的 componentDidCatch ,参数中的 error 是一串调用栈字符串, info 是一个结构,里面有一个 componentStack 字符串记录了组件序列。

8. 外围函数

React 对于 Component 的处理,前面介绍得差不多了,这里说的外围函数,是指还没有脱离 DOM 的那几个工具,比如前面用到的 ReactDOM.render(child, container) 就是一个典型。

  • ReactDOM.unmountComponentAtNode(container) , 从一个节点中删除组件。
  • ReactDOM.findDOMNode(component) ,找到组件“渲染的内容”所在的 DOM 节点,注意,不是组件自己所在的节点。
  • ReactDOM.createPortal(child, container) ,在 render() 中,取代返回 ReactElement ,而在一个确认的位置渲染出组件。(比如“弹层”的那种情况)

前 2 个方法是比较好理解的:

class MyComponent extends React.Component {
render(){
return <h1 onClick={this.clickHandler}>哈哈哈</h1>;
} clickHandler = () => {
var dom = ReactDOM.findDOMNode(this);
console.log(dom);
ReactDOM.unmountComponentAtNode($('div')[0]);
}
} ReactDOM.render(<MyComponent />, $('div')[0]);

8.1. ReactDOM.createPortal

ReactDOM.createPortal(child, container) 一般用在需要自己创建并维护真实 DOM 节点的地方:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
</head>
<body>
<div></div>
<script type="text/babel"> class MyComponent extends React.Component {
constructor(props){
super(props);
this.dom = document.createElement('div');
} componentDidMount(){
$('body').append($(this.dom));
} componentWillUnmount(){
$(this.dom).remove();
} clickHandler = () => {
var dom = ReactDOM.findDOMNode(this);
console.log(dom);
ReactDOM.unmountComponentAtNode($('div')[0]);
} render(){
return ReactDOM.createPortal(<div onClick={this.clickHandler}>{this.props.children}</div>, this.dom);
} } ReactDOM.render(<MyComponent><h1>弹出来的东西</h1></MyComponent>, $('div')[0]); </script>
</body>
</html>

上面的代码演示了 ReactDOM.createPortal 的使用,需要注意的是,即使一个组件的 render() 实现,不是 ReactElement 而是 ReactDOM.createPortal ,组件本身,还是需要依附于父组件,或者 ReactDOM.render() 的,即坑位在占住,这种时候,就有点“它在那里,它又不在那里”的感觉。

9. DOM 元素自己的那些东西

React 的实现的基础是自己实现的一套“树状节点结构”,然后再把这个结构放到浏览器中用真实的 DOM 展现出来。那么这中间,自己搞的一套,与浏览器环境下的一套,之间肯定会有少许的差异的,虽然 React 连事件这种都自己实现了一遍。

首先,对于节点的属性来说, React 的属性都是“驼峰式”的名字,与 DOM 自己的一些全小写属性名不同,比如, onclick 变成了 onClick , tabindex 变成了 tabIndex , SVG 中的属性 React 也支持。

属性名中的 class ,因为关键词因素,在 React 中变成了 className 。

style 在 React 中作了扩展实现的,给它一个对象值的话,可以得到正确的样式。

onChange 事件在 React 中的实现也作了增强,可以实时触发相应的回调。

label 标签的 for 属性,在 React 中可以使用 htmlFor 代替。

9.1. dangerouslySetInnerHTML

dangerouslySetInnerHTML 是一个特殊的属性,可以用于直接添加原始 HTML 内容:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
</head>
<body>
<div></div>
<script type="text/babel"> class MyComponent extends React.Component {
getHtml = () => {
return {__html: '<h1 style="color: red">哈哈哈</h1>'};
} render(){
return <div dangerouslySetInnerHTML={this.getHtml()} />
} } ReactDOM.render(<MyComponent />, $('div')[0]); </script>
</body>
</html>

9.2. ref

ref 也是 React 中的一个特殊属性,用于回调真实 DOM 节点的引用:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
</head>
<body>
<div></div>
<script type="text/babel"> class MyComponent extends React.Component {
clickHandler = () => {
console.log(this.h1);
} render(){
return <h1 ref={(node) => {this.h1 = node}} onClick={this.clickHandler}>哈哈</h1>
} } ReactDOM.render(<MyComponent />, $('div')[0]); </script>
</body>
</html>

ref 可以用于控制焦点,或者在节点上使用浏览器的其它一些 API ,比如音频,视频等。

10. 测试工具

React 因为有自己的一套树状态,所以,理论上它在测试方面有着得天独厚的优势,因为即使是在浏览器环境,不涉及 DOM 相关 API 的行为,只需要在那个树状态中的去操作,并检查操作后的状态就可以达到测试目的。当然,我们也不用关心 React 的一些实现细节,现在说测试,会有两方面的内容。一方面是 React 提供一些测试相关的工具方法,只是工具方法,如果你要建立完整的测试体系,还需要自己解决断言库,测试用例组件与调度等问题。另一方面,是看看非浏览器的渲染,如果可以完全摆脱浏览器环境,完成大部分的测试工作,那将是很美好的一件事。

11. 测试 API

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom-test-utils.development.js"></script>
</head>
<body>
<div></div>
<script type="text/babel"> class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {n: 0};
} clickHandler = () => {
this.setState((s) => ({n: s.n + 1}));
} render(){
return <h1 onClick={this.clickHandler}>{ this.state.n }</h1>
} } let component = ReactTestUtils.renderIntoDocument(<MyComponent />);
console.log(component);
let node = ReactDOM.findDOMNode(component)
console.log(component.state);
ReactTestUtils.Simulate.click(node);
console.log(component.state); console.log(ReactTestUtils.isElement(<MyComponent />));
console.log(ReactTestUtils.isElementOfType(<MyComponent />, MyComponent));
console.log(ReactTestUtils.isDOMComponent(ReactTestUtils.renderIntoDocument(<h1>xx</h1>)));
console.log(ReactTestUtils.isCompositeComponent(component));
console.log(ReactTestUtils.isCompositeComponentWithType(component, MyComponent));
console.log(ReactTestUtils.findRenderedDOMComponentWithTag(component, 'h1'));
console.log(ReactTestUtils.findRenderedComponentWithType(component, MyComponent)); </script>
</body>
</html>

引入额外的资源文件,相关的方法在浏览器是完全可以用的。

不过,完成的测试体系的话,还需要搭配其它工具来完成, React 的相关 API 只是提供了获取信息的方法。

11.1. 非浏览器渲染

在 nodejs 环境,可以使用 react-test-renderer 来完成对相关组件的处理,并按树结构遍历结果:

const React = require('react');
const TestRenderer = require('react-test-renderer'); class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {number: 0};
}
render(){
return <h1 onClick={ () => {this.setState((s) => ({number: s.number + 1}))} }>{this.state.number}</h1>;
}
} const testRenderer = TestRenderer.create(<MyComponent />);
testRenderer.root.findByType('h1').props.onClick();
console.log(testRenderer.root.findByType('h1').children);
console.log(testRenderer.root.instance.state);

这部分完整的 API 在 https://reactjs.org/docs/test-renderer.html 。

在非浏览器环境下,“模拟点击”这种操作是没有意义的,但是 onClick 本身并没有说一定是“点击”,它只是指明了一个回调函数而已,浏览环境下是点击触发,那么纯编程环境下,直接调用即可。

12. Redux

12.1. 基本概念

Redux 并不是 React 必不可少的一部分,它只是随着 React 出来的一套模式,并且带了一个这个模式的实现: https://redux.js.org/ 。 简单来说,这个模式是着眼于“组件状态及组件间通信”的,换句话,它定义了一种“相对独立的组件句柄”。

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>Redux</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body> <script type="text/javascript">
var store = Redux.createStore(function reducer(state, action){
console.log('here', state);
if(action.type === 'HAHA'){ return 'OK' }
return 'ERROR';
});
console.log(store.getState());
store.subscribe(function render(){
console.log('subscribe');
console.log(store.getState());
})
store.dispatch({type: 'HAHA'});
</script> </body>
</html>

Redux 的流程本身非常简单,如上所示,通过一个 reducer 初始化一个 store , 这个 store 先 subscribe 一些动作,然后使用时在需要的时候 dispatch 出一个 action 就好了。 dispatch 的对应逻辑是 reducer 中的事,而 reducer 执行完就轮到 subscribe 的回调了,就是这样的一个执行循环。

12.2. React-Redux

React-Redux 是 Redux 在 React 上的一个实现,直观点说,就是结合了 React 的 Component 机制的一套 Redux 流程。它的核心点,我个人的理解是下面几点:

  • 首先,组件本身进行两分类,一类“纯组件”,一类“通信组件”。“纯组件”的意思,就是参入 props 之后,它自己就可以完全独立工作的,里面尽量不包含业务逻辑。而“通信组件”的作用,就是在“纯组件”之上,加入具体业务逻辑,去完成各“纯组件”的调度。
  • 在实现“通信组件”上,并不推荐你去手写,而是根据 Redux 的思路, React-Redux提供相应用的包装 API ,来完成 State -> Props 和 Dispatch -> Props 的这个过程。
  • 进一步说,“纯组件”的行为,完全由 props 控制,无论是相应的数据输入,还是一些事件回调的输出。状态性的一些东西,则由上层的“通信组件”,通过 state 和 dispatch 来维护,上层通信组件的状态性的数据,会更新到“纯组件”的 props 上,来完成页面重渲染。
  • 本质上,是实现上的一种垂直抽象分层, Redux 的具体 API 不是重点。

具体代码:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/redux.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-redux.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel"> const Title = props => (
<h1 onClick={props.onClick}>{props.name}</h1>
) const mapStateToProps = state => {
return {...state};
} const mapDispatchToProps = dispatch => {
return {
onClick: id => {
dispatch({type: "CLICK"});
}
}
} const VisibleTitle = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Title); let store = Redux.createStore( (state, action) => {
if(action.type === 'CLICK'){
return {name: '点击之后的'};
}
return {name: '初始化的'};
}); const App = () => (
<ReactRedux.Provider store={store}>
<VisibleTitle />
</ReactRedux.Provider>
) ReactDOM.render(<App />, document.getElementById('app'));
</script>
</body>
</html>

上面的组件实现的效果很简单,就是点击之后,改变文本。但是经过 React-Redux 分拆之后,感觉复杂了好多。不过,直观地也可以看出,分拆之后, Title 这个组件变“纯”了,它的行为一目了解,没有什么“点击之后,改变文本”这类逻辑,只有单纯的接受输入,然后渲染组件。操作上的逻辑,则由外层的 VisibleTitle 来完成。

VisibleTitle 通过它的 state 状态性数据,来控制下面的 Title 的渲染。 state 则是通过 Redux 的 store 来承接的。进一步, state 到 Title 的 props ,可能只是 Title 需要输入的一部分(数据部分),另一部分涉及“反馈”到 state 变化的 props ,则由 Redux 的 dispatch - action 来承接。

说的这两部分,也就对应着 mapStateToProps 和 mapDispatchToProps ,一个处理数据,一个调度 dispatch 的执行。然后使用 connect 这个现成的方法生成包裹在 Title 之上的 VisibleTitle 。

当然,事情到这里还没有结束,再上层,还有一个 store , Provider 是 React-Redux 提供的东西,目的是给里面的组件提供 store 。而得到 store ,又需要 Reducer , dispatch 出的 action ,及 state 的变化实际上是在这里处理。

12.3. Redux 中间件

Redux 的设计中,所有的状态变化是通过 dispatch 这一个点完成的(并且这个 API 又非常简单),自然,围绕这一个点,就可以很容易完成“套圈”。

let next = store.dispatch;
store.dispatch = function(action){
console.log(action);
next(action);
}

这都完全不需要特别设计,你自己就可以随便搞。

不过从 Monkeypatching 作为切入,官方的 https://redux.js.org/docs/advanced/Middleware.html 这份文档写得非常好,循序渐进,推荐一读。

最后,官方 API 在这上面的支持,或者说推荐作法,是通过高阶函数定义 Middleware ,然后使用 applyMiddleware 挂到 store 里面去。

const logger = store => next => action => {
console.log('dispatching', action, store.getState());
let result = next(action);
console.log('next state', store.getState());
return result
}

这是定义,使用时:

import { createStore, combineReducers, applyMiddleware } from 'redux'
let store = createStore( reducer, applyMiddleware(logger) )

用前面的简单例子可以试试:

function logger(store) {
return function(next){
return function(action){
console.log('dispatching', action, store.getState());
var result = next(action);
console.log('next state', store.getState());
return result
}
}
} var store = Redux.createStore(function reducer(state, action){
if(action.type === 'HAHA'){ return 'OK' }
return 'ERROR';
}, Redux.applyMiddleware(logger)); store.subscribe(function render(){
console.log('sub', store.getState());
})
store.dispatch({type: 'HAHA'});

12.4. 异步流程与 redux-promise

了解了中间件,再来看异步流程,就没什么新问题了。

在 Redux 中,state 的维护是在 reducer 中的,从前面的例子可以看出, reducer 本身的设计,是没有考虑异步情况的,它的结构: (state, action) => newState 是同步的。

如果我们在某个 dispatch 之后,需要异步获取数据(事实上这很常见),并把这个东西更新到 store 中去,我们就需要额外做点事,比较很容易想到的一个办法:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>Redux</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body> <script type="text/javascript">
var store = Redux.createStore(function reducer(state, action){
if(action.type === 'HAHA'){
setTimeout(function(){
store.dispatch({type: 'FETCH', payload: '123'});
}, 3000);
return 'LOADING';
}
if(action.type === 'FETCH'){
return action.payload;
}
return 'ERROR';
});
store.subscribe(function render(){
console.log('sub', store.getState());
})
store.dispatch({type: 'HAHA'});
</script> </body>
</html>

简单来说,在 dispatch 之后,我们异步发出请求,并在当前至少有对 dispatch 的引用,然后返回一个中间状态的 state ,甚至是没有任何更改的 state 。在异步响应之后,再次 dispatch ,同时把异步响应的数据放到 action 中传递出去。

这种方式,虽然绕了一点,但是本身没毛病,不过要多定义出来一个 dispatch 类型,是很烦人就是了。

考虑前面讲过的“中间件”机制,我们很容易对 dispatch 的行为作一些扩展,加入对异步的支持。

先看“中间件”的样子:

const logger = store => next => action => {
console.log('dispatching', action, store.getState());
let result = next(action);
console.log('next state', store.getState());
return result
}

很显然,对于 dispatch 传入的 action 这个东西,我们可以先判断它的特征,或者说状态,根据既定规则决定在什么时候作 next(action) 。

事实上, redux-promise 的代码就几行:

import { isFSA } from 'flux-standard-action';

function isPromise(val) {
return val && typeof val.then === 'function';
} export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)
? action.then(dispatch)
: next(action);
} return isPromise(action.payload)
? action.payload.then(
result => dispatch({ ...action, payload: result }),
error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
}
)
: next(action);
};
}

做的事,就是当 dispatch 出去的东西,里面有一个 then 成员,并且这个成员是一个函数的话,那么 reducer 会返回这个函数的调用结果,并且调用这个函数时传入的是当前的 dispatch 。

简单演示一下大概是:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>Redux</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body> <script type="text/javascript">
var store = Redux.createStore(function reducer(state, action){
if(!!action.then){
return action.then(store.dispatch.bind(store));
}
return 'GOT: ' + action.payload;
});
store.subscribe(function render(){
console.log('sub', store.getState());
}) store.dispatch({type: '123', payload: 'null'});
setTimeout(function(){
store.dispatch({type: 'async', then: function(dispatch){
setTimeout(function(){
dispatch({type: 'xxx', payload: 'async data'});
}, 3000);
}});
}, 2000); </script> </body>
</html>

12.5. Redux 流程中的重复计算问题

前面一个例中,有一个 mapStateToProps ,把例子改一点点看看:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/redux.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-redux.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel"> const getName = (name, count) => {
console.log('getName');
return name + count;
} const Title = props => (
<div>
<h1 onClick={props.onClick}>{props.name}</h1>
<h1 onClick={props.onOtherClick}>另外的 {props.name}</h1>
</div>
) const mapStateToProps = (state, props) => {
console.log('mapStateToProps');
return {
name: getName(state.name, state.count)
}
} const mapDispatchToProps = dispatch => {
return {
onClick: () => {
dispatch({type: "CLICK"});
},
onOtherClick: () => {
dispatch({type: "OTHER_CLICK"});
}
}
} const VisibleTitle = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Title); let store = Redux.createStore( (state = {name: '初始值', count: 0}, action) => {
if(action.type === 'CLICK'){
return { ...state, name: '点击之后的', count: state.count + 1};
}
if(action.type === 'OTHER_CLICK'){
return { ...state, other: '什么东西'};
}
return { ...state };
}); const App = () => (
<ReactRedux.Provider store={store}>
<VisibleTitle />
</ReactRedux.Provider>
) ReactDOM.render(<App />, document.getElementById('app'));
</script>
</body>
</html>

上面的例子,简单来说,“纯组件”需要一个 name 来显示,而这个 name 的来自于外套组件的 store 中的 name 和 count 连在一起的。

执行这个例子,可以看出一个重复执行的问题,当你点击第二行“另外的”那块文本时,是做的 onOtherClick 回调,这个过程并不会更改 count 值,也就是说,“纯组件”需要的 name 并不会因为这个操作而有所改变。但是, getName() 这个函数却还是一遍又一遍地执行了,这就是所谓的重复执行的问题。(如果这样的函数很多,会影响到页面性能的,特别是这些函数的逻辑有一点复杂的时候)

其实看到这里,我个觉得 React 中的这个问题还是非常尴尬的,它自己内部的虚拟节点,靠自己的 diff 算法实现高效的更新策略,但是外面的 state 本身的维护又成了可能的问题。

回到重复执行的问题,它并没有什么本质的解决办法,只可能进一步拆分 getName() ,分离变量的情况下,作一些执行层面的优化,简单说,先作变量是否变化的判断,然后再决定是否继续执行。这样用额外判断的成本,换取可能的省略后继执行的效益。大概是:

const getName = ( () => {
let lastName = null;
let lastCount = null;
let lastResult = null;
return (name, count) => {
console.log('getName');
if(name === lastName && count === lastCount){ return lastResult }
lastName = name;
lastCount = count;
lastResult = name + count;
return lastResult;
}
})();

这个样子,像是中间加了一个缓存层。

把这个形式封装一下,就是说先把“变量”的获取提取出来,然后加一个中间缓存层。官方推荐的实现是 reselect ,做的事就是这样的: https://github.com/reactjs/reselect 。

用 reselect 把前面的代码调整一下,就是:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<title>React</title>
<script crossorigin src="https://s.zys.me/js/jq/jquery.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-dom.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/babel.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/redux.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/react-redux.min.js"></script>
<script crossorigin src="https://s.zys.me/js/react/reselect.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel"> const getName = Reselect.createSelector(
[
(state, props) => {console.log('reselect'); return state.name},
(state, props) => state.count
],
(name, count) => {
console.log('getName');
return name + count
}
); const Title = props => (
<div>
<h1 onClick={props.onClick}>{props.name}</h1>
<h1 onClick={props.onOtherClick}>另外的 {props.name}</h1>
</div>
) const mapStateToProps = (state, props) => {
console.log('mapStateToProps');
return {
name: getName(state, props)
}
} const mapDispatchToProps = dispatch => {
return {
onClick: () => {
dispatch({type: "CLICK"});
},
onOtherClick: () => {
dispatch({type: "OTHER_CLICK"});
}
}
} const VisibleTitle = ReactRedux.connect( mapStateToProps, mapDispatchToProps )(Title); let store = Redux.createStore( (state = {name: '初始值', count: 0}, action) => {
if(action.type === 'CLICK'){
return { ...state, name: '点击之后的', count: state.count + 1};
}
if(action.type === 'OTHER_CLICK'){
return { ...state, other: '什么东西'};
}
return { ...state };
}); const App = () => (
<ReactRedux.Provider store={store}>
<VisibleTitle />
</ReactRedux.Provider>
) ReactDOM.render(<App />, document.getElementById('app'));
</script>
</body>
</html>

Reselect.createSelector 是一个高阶函数,它接受的第一个参数,是一个列表,里面就是分拆出来的,获取“变量”的方法,第二个参数就是接受变量的主要逻辑。因为变量已经明确拆分出来的,所以自然地,针对变量作判断并应用缓存机制就好办了。

这里注意一下,虽然表面上 getName() 的调用少了,但是却额外多了 reselect 的调用,本质上来说,只是改善的重复调用的损耗,并没有避免它。

原文转载自https://www.zouyesheng.com/react.html#toc3

(转)React学习笔记(干货满满)的更多相关文章

  1. react学习笔记1--基础知识

    什么是react A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES[React是一个用于构建用户界面的JavaScript库.] React之所以快, ...

  2. React学习笔记--程序调试

    React学习笔记 二 程序调试   前面我们搭建好了React的基本开发环境,可以编写基本的React js程序了.但完成的开发环境肯定包含调试器,怎么调试用React编写的JS程序呢?有浏览器,比 ...

  3. React学习笔记(一)- 入门笔记

    React入门指南 作者:狐狸家的鱼 本文链接:React学习笔记 GitHub:sueRimn 1.组件内部状态state的修改 修改组件的每个状态,组件的render()方法都会再次运行.这样就可 ...

  4. React学习笔记(七)条件渲染

    React学习笔记(七) 六.条件渲染 使用if或条件运算符来创建表示当前状态的元素. 可以使用变量来存储元素.比如: let button = null; if (isLoggedIn) { but ...

  5. React学习笔记(六)事件处理

    React学习笔记(六) 五.事件处理 React事件绑定属性的命名采用驼峰写法,不同于传统DOM全部小写. 如果采用JSX的语法,事件函数需要用大括号{}包裹函数名,不同于传统DOM字符串小括号的方 ...

  6. React学习笔记(五)State&声明周期

    React学习笔记(五) 四.State&声明周期 可以为组件添加"状态(state)".状态与属性相似,但是状态是私有的,完全受控于当前组件. 局部状态就是只能用于类(定 ...

  7. React学习笔记 - 组件&Props

    React Learn Note 4 React学习笔记(四) 标签(空格分隔): React JavaScript 三.组件&Props 组件可以将UI切分成一些独立的.可复用的部件,这样你 ...

  8. React学习笔记 - 元素渲染

    React Learn Note 3 React学习笔记(三) 标签(空格分隔): React JavaScript 二.元素渲染 元素是构成react应用的最小单位. 元素是普通的对象. 元素是构成 ...

  9. React学习笔记 - JSX简介

    React Learn Note 2 React学习笔记(二) 标签(空格分隔): React JavaScript 一.JSX简介 像const element = <h1>Hello ...

  10. React学习笔记 - Hello World

    React Learn Note 1 React学习笔记(一) 标签(空格分隔): React JavaScript 前.Hello World 1. 创建单页面应用 使用Create React A ...

随机推荐

  1. 从入门到掌握 - 系统学习shell语言

    简介 什么是 shell Shell是一种程序或命令行解释程序,用于解释用户直接输入的用户命令或从文件中读取的用户命令,然后将 它们传递给操作系统以进行操作或处理.要注意,这个过程是解释而不编译脚本, ...

  2. 浅谈 Tarjan 算法

    目录 简述 作用 Tarjan 算法 原理 出场人物 图示 代码实现 例题 例题一 例题二 例题三 例题四 例题五 总结 简述 对于初学 Tarjan 的你来说,肯定和我一开始学 Tarjan 一样无 ...

  3. 谈谈OKHttp的几道面试题

    来吧,今天说说常用的网络框架OKHttp,也是现在Android所用的原生网络框架(Android 4.4开始,HttpURLConnection的底层实现被Google改成了OkHttp),GOGO ...

  4. Typora + picgo + sm.ms 图床设置笔记

    Typora + picgo + sm.ms 图床设置笔记 编辑于2020-03-26 本文部分内容在作者教程的基础上进行了二次编辑,如有重复,纯属必然 在此感谢大佬们的无私付出与分享 之前 用了 g ...

  5. expect ':' at 0, actual = (JSON转化异常解决)

    这个报错我的问题主要是前端得到的JSON格式不是标准的JSON串,所以会报这个错, 解决办法 需要使用JSON.toJSONString()转换为标准的字符串

  6. z-index属性详解

    z-index属性详解 目录 z-index属性详解 一.定义和用法 二.代码 三.效果图 一.定义和用法 z-index属性指定一个元素的堆叠顺序,也就是z轴 position属性定义的是x轴和y轴 ...

  7. Java项目——嗖嗖移动业务大厅

    嗖嗖移动业务大厅包类(如下图): SosoMgr: 1 package cn.biz; 2 3 import java.util.Scanner; 4 5 import cn.common.Commo ...

  8. js给多级复杂动态变量赋值

    1 function SetVal(field, val) { 2 var arr = field.split("."); 3 var str = arr[0]; 4 if (wi ...

  9. 使用KepServerEx进行数据模拟

    KepServerEx是一款在工业控制中比较常见的数据采集服务软件之一,提供了多种类型的驱动,具有比较广泛的适用性.很多厂商和个人都会选择用它来做OPCServer.在项目的实施或测试过程中,我们有时 ...

  10. day002|python基础回顾2

    目录 00 上节课复习 01 基本数据类型 02 与用户交互 03 运算符 04 流程运算之if判断 05 流程判断之while循环 06 TEST 00 上节课复习 ""&quo ...