#react

TIP

JSX其实只是一种语法糖,最终会通过babeljs转译成createElement语法

# 一、react元素

# 1 特点

  • React元素是构成React应用的最小单位
  • React元素用来描述你在屏幕上看到的内容
  • React元素事实上是普通的JS对象
  • ReactDOM来确保浏览器中的DOM数据和React元素保持一致

# 2 jsx转化react元素

简单版本

// jsx
let element = <h1>hello</h1>;

// react元素
let element = React.createElement("h1", null, "Hello");

console.log(JSON.stringify(element, null, 4));

/**
{
	"type": "h1",
	"props": {
		"children": "Hello"
	}
}
 */

复杂版本

// jsx
let element = (
  <h1 className="title" style={{color:'red'}}>
    <span>hello</span>world
  </h1>
);

// react元素
/**
 * 参数1 标签的类型 h1 span div
 * 参数2 属性的JS对象
 * 参数3往后的都是儿子们
 */
let element = React.createElement("h1", {
  className: "title",
  style: {
    color: 'red'
  }
}, React.createElement("span", null, "hello"), "world");

console.log(JSON.stringify(element, null, 4));

/**
{
	"type": "h1",
	"props": {
		"className": "title",
		"style": {
			"color": "red"
		},
   		"children": [
      		{
				"type": "span",
				"props": {
					"children": "hello"
				}
			},
			"world"
    	]
  	}
}
 */

# 3 实现createElement

/**
 * @param {*} type 元素的类型
 * @param {*} config 配置的属性对象
 * @param {*} children 第一个儿子
 */
function createElement(type, config, children) {
	// 删除多余对象
	if (config) {
		delete config._owner;
		delete config._store;
	}
	let props = {...config};

	// 截取儿子数据
	if (arguments.length > 3) {
		children = Array.prototype.slice.call(arguments, 2);
	}
	// children可能是数组(多于1个儿子)
	//  也可能是一个字符串或者数字,也可能是一个null,也可能是一个react元素 
	 props.children = children;
	//  返回React元素,也就是虚拟DOM
	 return {
		 type, // 元素的类型
		 props // 元素的属性
	 }
}

let React = {
	createElement
}

export default React;

# 二、组件

# 1 函数组件

  • 函数组件的props参数可以为任意值
  • 函数组件接收一个单一的props对象并返回了一个React元素
import React from 'react'; // react核心库
import ReactDOM from 'react-dom'; // react的dom渲染库

/**
 * 定义一个函数组件 名字首字母必须要大写
 * 函数内部的jsx语法被转化为react.createElement元素
 * @param { } props 属性对象
 */
function Welcome(props){
  return <h1>Hello, {props.name}</h1>;
}

/**
function Welcome(props) {
  return React.createElement("h1", null, "Hello, ", props.name);
} 
*/

// 调用组件,组件名称和属性会被当成参数传入到react.createElement中
let element = <Welcome name="world"/>

/**
var element = React.createElement(Welcome, {
  name: "world"
});
*/

ReactDOM.render(
	element, 
	document.getElementById('root')
);

# 2 类组件

import React from 'react'; // react核心库
import ReactDOM from 'react-dom'; // react的dom渲染库

/**
 * 定义一个类组件
 */
class Welcome extends React.Component{
	// this.props = {name:'world'}
  	render(){
    	return <h1>hello,{this.props.name}</h1>;
  	}
}

// 调用组件,组件名称和属性会被当成参数传入到react.createElement中
let element = <Welcome name="world"/>

ReactDOM.render(
	element, 
	document.getElementById('root')
);

# 3 总结

  • React元素可能是字符串(原生DOM类型),也可能一个函数(函数组件),也可能是一个类(类组件)
  • 在定义组件元素的时候,会把JSX所有的属性封装成一个props对象传递给组件
  • 组件的名称一定要首字母大写,React是通过首字母来区分原生还是自定义组件
  • 组件要先定义,再使用
  • 组件要返回并且只能返回一个React根元素,否则会报错:JSX expressions must have one parent element

# 三、状态

  • 组件的数据来源有两个地方,分别是属性对象和状态对象
  • 属性是父组件传递过来的(默认属性,属性校验),不能修改
  • 状态是内部产生的,可以改,状态只能用在类组件里
  • 唯一能给this.state赋值的地方就是构造函数,只能初始值
  • 其它地方要想改变状态只能调用setState()方法
  • 每当你调用setState方法的时候就会引起组件的刷新,组件会重新调用一次render方法,得到新虚拟DOM,进行DOM更新
  • 属性和状态的变化都会影响视图更新

# 1 使用

import React from 'react'; // react核心库
import ReactDOM from 'react-dom'; // react的dom渲染库

class Clock extends React.Component {
	constructor(props) {
		super(props);
		// 初始化状态
		this.state = {
			date:new Date()
		};
        setInterval(this.tick, 1000);
	}
	
	tick=()=>{
		// Do not mutate state directly. Use setState()
		// 通过setState()修改状态
		this.setState({date:new Date()});
	}
	
    render(){
        return (
            <div>
                <h1>Hello</h1>
                <h2>当前的时间:{this.state.date.toLocaleTimeString()}</h2>
            </div>
        )
    }
}

let element = <Clock/>

ReactDOM.render(
	element, 
	document.getElementById('root')
);

# 2 异步更新

  • 状态是封闭的,只有组件自己能访问和修改
  • 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用,进行批量执⾏, 多key一次执⾏,相同key合并
  • 可能是异步的,在事件处理函数中或生命周期函数中批量更新是异步的,其它地方都是直接同步更新的
  • 如果要同步获取最新状态值,三种⽅方式:
    1. 传递回调函数给setState
    2. 写在定时器中
    3. 写在JS的原生事件中
import React from 'react'; // react核心库
import ReactDOM from 'react-dom'; // react的dom渲染库

class Counter extends React.Component {
	constructor(props) {
		super(props);
		// 初始化状态
		this.state = {
			number: 0
		};
	}
	
	handleClick = () => {
		// 对象进行修改
		this.setState({
			number: this.state.number + 1
		});
		// 没改之前的值
		console.log(this.state.number);// 0
		
		// 函数进行修改
		this.setState((state) => (
            { number: state.number + 1 }
		));
		// 批量执行
		this.setState((state) => (
            { number: state.number + 1 }
        ));
	}
	
    render(){
        return (
            <div>
                <p>number:{this.state.number}</p>
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
}

let element = <Counter/>

ReactDOM.render(
	element, 
	document.getElementById('root')
);

# 3 批量更新器

// 更新器
class Updater {
    constructor(){
		// 初始状态
        this.state={name:'test', number:0};
        this.queue = [];
	}
	// 更新状态方法
    setState(newState){
        this.queue.push(newState);
	}
	// 批量更新
    flush(){
        for(let i=0;i<this.queue.length;i++){
            let update = this.queue[i];
            if(typeof update === 'function'){
                this.state = {...this.state,...update(this.state)};
            }else{
                this.state = {...this.state,...update};
            }
        }
    }
}
let updater = new Updater();
updater.setState({number: 1}); 
updater.setState((previousState)=>({number: previousState.number+1}));
updater.setState({number: 2}); 
updater.setState({number: 3}); 

updater.flush();
console.log(updater.state); // { name: 'test', number: 3 }

# 4 同步更新器

// 更新器
class Updater {
    constructor(){
		// 初始状态
        this.state={name:'test', number:0};
        this.queue = [];
	}
	// 更新状态方法
    setState(update){
        if(typeof update === 'function'){
            this.state = {...this.state,...update(this.state)};
        }else{
            this.state = {...this.state,...update};
        }
    }
}
let updater = new Updater();
updater.setState({number: 1}); 
console.log(updater.state); // { name: 'test', number: 1 }
updater.setState((previousState)=>({number: previousState.number+1}));
console.log(updater.state); // { name: 'test', number: 2 }
updater.setState({number: 3}); 
console.log(updater.state); // { name: 'test', number: 3 }
updater.setState({number: 4}); 
console.log(updater.state); // { name: 'test', number: 4 }

# 四、事件处理

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
  • 你不能通过返回 false 的方式阻止默认行为。你必须显式的使用preventDefault
import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
	// 事件处理函数
	handleClick(e) {
		// 阻止默认事件
        e.preventDefault();
        alert('The link was clicked.');
    }
	render() {
        return (
            <a href="http://www.baidu.com" onClick={this.handleClick}>
                Click me
          </a>
        );
    }
}

ReactDOM.render(
	<Counter/>, 
	document.getElementById('root')
);

# this指向

  • 事件函数写成箭头函数
  • 事件函数为普通函数,调用用匿名函数
  • 在构造函数中使用bind绑定this,改变this指向
  • 如果要传参数,只能使用匿名函数
import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
	constructor(props){
        super(props);
       // this.handleClick = this.handleClick.bind(this);
    }
    handleClick = ()=>{
        console.log('this is:', this);
	}
	handleClick2() {
		console.log('this is:', this);
	}
	handleClick3 = (amount)=>{
        console.log('this is:', this, amount);
    }
	render() {
        return (
			<div>
				<button onClick={this.handleClick}>+</button>
				<button onClick={() => this.handleClick2()}>-</button>
				<button onClick={this.handleClick3.bind(this,3)}>click</button>
			</div>
        );
    }
}

ReactDOM.render(
	<Counter/>, 
	root = document.getElementById('root')
);

# 五、生命周期

# 1 旧版生命周期

初始化 》 挂载 》 更新 》 卸载

react

  • 状态更新生命周期变化
import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
	static defaultProps = {
		name: '计算器'
	}

	constructor(props) {
		super(props);
		this.state = {number: 0};
		console.log("Counter 1.constructor 初始化属性和状态");
	}

	handleClick = () => {
		this.setState({number: this.state.number + 1});
	}

	componentWillMount() {
		console.log("Counter 2.componentWillMount 组件将要挂载");
	}

	componentDidMount() {
		console.log("Counter 4.componentWillMount 组件挂载完成");
	}

	shouldComponentUpdate(nextProps, nextState) {
		console.log('Counter 5.shouldComponentUpdate 决定组件是否需要更新?');
		// 偶数为true 需要更新,奇数为false 不需要更新
		return nextState.number % 2 === 0;
	}

	componentWillUpdate() {
		console.log('Counter 6.componentWillUpdate 组件将要更新');
	}

	componentDidUpdate() {
		console.log('Counter 7.componentDidUpdate 组件更新完成');
	}

	render() {
		console.log("Counter 3.render 挂载");
		return (
			<div>
				<p>{this.state.number}</p>
				<button onClick={this.handleClick}>+</button>
			</div>
		)
	}
}

ReactDOM.render(<Counter />, document.getElementById('root'))

// 初始化挂载
Counter 1.constructor 初始化属性和状态
Counter 2.componentWillMount 组件将要挂载
Counter 3.render 挂载
Counter 4.componentWillMount 组件挂载完成

// 点击加号,返回false
Counter 5.shouldComponentUpdate 决定组件是否需要更新?

// 点击加号,返回true
Counter 5.shouldComponentUpdate 决定组件是否需要更新?
Counter 6.componentWillUpdate 组件将要更新
Counter 3.render 挂载
Counter 7.componentDidUpdate 组件更新完成
  • 状态和属性一起变化的生命周期
import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
	static defaultProps = {
		name: '计算器'
	}

	constructor(props) {
		super(props);
		this.state = {number: 0};
		console.log("Counter 1.constructor 初始化属性和状态");
	}

	handleClick = () => {
		this.setState({number: this.state.number + 1});
	}

	componentWillMount() {
		console.log("Counter 2.componentWillMount 组件将要挂载");
	}

	componentDidMount() {
		console.log("Counter 4.componentWillMount 组件挂载完成");
	}

	shouldComponentUpdate(nextProps, nextState) {
		console.log('Counter 5.shouldComponentUpdate 决定组件是否需要更新?');
		// 偶数为true 需要更新,奇数为false 不需要更新
		return nextState.number % 2 === 0;
	}

	componentWillUpdate() {
		console.log('Counter 6.componentWillUpdate 组件将要更新');
	}

	componentDidUpdate() {
		console.log('Counter 7.componentDidUpdate 组件更新完成');
	}

	render() {
		console.log("Counter 3.render 挂载");
		return (
			<div>
				<p>{this.state.number}</p>
				{this.state.number === 4 ? null : <ChildCounter count={this.state.number} />}
				<button onClick={this.handleClick}>+</button>
			</div>
		)
	}
}

class ChildCounter extends React.Component {
	componentWillUnmount() {
		console.log('ChildCounter 6.componentWillUnmount 组件将要卸载');
	}

	componentWillMount() {
		console.log('ChildCounter 1.componentWillMount 组件将要挂载');
	}

	componentDidMount() {
		console.log('ChildCounter 3.componentDidMount 组件挂载完成');
	}

	componentWillReceiveProps(newProps) {
		console.log('ChildCounter 4.componentWillReceiveProps 组件将要接收到新的属性');
	}

	shouldComponentUpdate(nextProps, nextState) {
		console.log('ChildCounter 5.shouldComponentUpdate 决定组件是否需要更新?');
		// 3的倍数就更新,否则就不更新
		return nextProps.count % 3 === 0;
	}

	componentWillUpdate() {
		console.log('ChildCounter 7.componentWillUpdate  组件将要更新');
	}

	componentDidUpdate() {
		console.log('ChildCounter 8.componentDidUpdate 组件更新完成');
	}

	render() {
		console.log('ChildCounter 2.render');
		return (<div>{this.props.count}</div>)
	}
}

ReactDOM.render(<Counter />, document.getElementById('root'))

# 2 新版生命周期

# 六、上下文

# 七、高阶组件