* CREATE-ELEMENT:创建JSX对象
* 参数:至少两个 TYPE/PROPS,CHILDREN这个部分可能没有可能有多个
function createElement(type, props, ...childrens) { let ref, key; if ('ref' in props) { ref = props['ref']; props['ref'] = undefined; } if ('key' in props) { key = props['key']; props['key'] = undefined; } return { type, props: { ...props, children: childrens.length <= 1 ? (childrens[0] || '') : childrens }, ref, key }; } function render(objJSX, container, callBack) { let {type, props} = objJSX, {children} = props; let newElement = document.createElement(type); for (let attr in props) { if (!props.hasOwnProperty(attr)) break; let value = props[attr]; if (value == undefined) continue;//=>NULL OR UNDEFINED switch (attr.toUpperCase()) { case 'CLASSNAME': newElement.setAttribute('class', value); break; case 'STYLE': for (let styleAttr in value) { if (value.hasOwnProperty(styleAttr)) { newElement['style'][styleAttr] = value[styleAttr]; } } break; case 'CHILDREN': /* * 可能是一个值:可能是字符串也可能是一个JSX对象 * 可能是一个数组:数组中的每一项可能是字符串也可能是JSX对象 */ //->首先把一个值也变为数组,这样后期统一操作数组即可 !(value instanceof Array) ? value = [value] : null; value.forEach((item, index) => { //->验证ITEM是什么类型的:如果是字符串就是创建文本节点, //->如果是对象,我们需要再次执行RENDER方法,把创建的元素放到最开始创建的大盒子中 if (typeof item === 'string') { let text = document.createTextNode(item); newElement.appendChild(text); } else { render(item, newElement); } }); break; default: newElement.setAttribute(attr, value); } } container.appendChild(newElement); callBack && callBack(); } export { createElement, render };
不管是VUE还是REACT框架,设计之初都是期望我们按照“组件/模块管理”的方式来构建程序的,也就是把一个程序划分为一个个的组件来单独处理
[优势] 1.有助于多人协作开发 2.我们开发的组件可以被复用
函数声明式组件
基于继承COMPONENT类来创建组件
SRC -> COMPONENT :这个文件夹中存放的就是开发的组件
import React from 'react'; import ReactDOM from 'react-dom'; import Dialog from './component/Dialog'; ReactDOM.render(<div> {/*注释:JSX中调取组件,只需要把组件当做一个标签调取使用即可(单闭合和双闭合都可以)*/} <Dialog con='哈哈哈' style={{color: 'red'}}/> <Dialog con='嘿嘿嘿' lx={1}> {/*属性值不是字符串,我们需要使用大括号包起来*/} <span>1</span> <span>2</span> </Dialog> </div>, root);
* 函数式声明组件
* 1.函数返回结果是一个新的JSX(也就是当前组件的JSX结构)
*
* 2.PROPS变量存储的值是一个对象,包含了调取组件时候传递的属性值(不传递是一个空对象)
import React from 'react'; //=>每一个组件中都要导入REACT,因为需要基于它的CREATE-ELEMENT把JSX进行解析渲染呢 export default function Dialog(props) { let {con, lx = 0, children, style = {}} = props, title = lx === 0 ? '系统提示' : '系统警告'; //=>children:可能有可能没有,可能只是一个值,也可能是一个数组,可能每一项是一个字符串,也可能是一个对象等 (代表双闭合组件中的子元素) return <section style={style}> <h2>{title}</h2> <div>{con}</div> {/*把属性中传递的子元素放到组件中的指定位置*/} {/*{children}*/} {/*也可以基于REACT中提供的专门遍历CHILDREN的方法来完成遍历操作*/} { React.Children.map(children, item => item) } </section>; };
知识点: CREATE-ELEMENT在处理的时候,遇到一个组件,返回的对象中:TYPE就不在是字符串标签名了,而是一个函数(类),但是属性还是存在PROPS中
RENDER渲染的时候,我们需要做处理,首先判断TYPE的类型,如果是字符串,就创建一个元素标签,如果函数或者类,就把函数执行,把PROPS中的每一项(包含CHILDREN)传递给函数 在执行函数的时候,把函数中RERURN的JSX转换为新的对象(通过CREATE-ELEMENT),把这个对象返回;紧接着RENDER按照以往的渲染方式,创建DOM元素,插入到指定的容器中即可
import React from 'react'; export default function Dialog(props) { let {type, content, children} = props; //=>自己处理的一些样式 let objStyle = { width: '50%', margin: '10px auto' }; //=>类型的处理 let typeValue = type || '系统提示'; if (typeof type === 'number') { switch (type) { case 0: typeValue = '系统提示'; break; case 1: typeValue = '系统警告'; break; case 2: typeValue = '系统错误'; break; } } return <section className='panel panel-default' style={objStyle}> <div className='panel-heading'> <h3 className='panel-title'>{typeValue}</h3> </div> <div className='panel-body'>{content}</div> {/*如果传递了CHILDREN,我们把内容放到尾部中,不传递什么都不显示*/} { children ? <div className='panel-footer'> {React.Children.map(children, item => item)} </div> : null } </section>;
import React from 'react'; import ReactDOM from 'react-dom'; /* * 1.我们一般都把程序中的公用样式放到INDEX中导入,这样在其它组件中也可以使用了(WEBPACK会把所有的组件最后都编译到一起,INDEX是主入口) * * 2.导入bootstrap,需要导入的是不经过压缩处理的文件,否则无法编译(真实项目中bootstrap已经是过去事,我们后期项目中使用组件都是用ANT来做) */ import './static/css/reset.min.css'; import 'bootstrap/dist/css/bootstrap.css'; import Dialog from "./component/Dialog"; ReactDOM.render(<main> <Dialog content='马少帅长得很帅'/> <Dialog type={2} content='刘天瑞写的代码报错了!'/> <Dialog type='请登录' content={ <div> <input type="text" className="form-control" placeholder="请输入用户名"/> <br/> <input type="password" className="form-control" placeholder="请输入密码"/> </div> }> <button className='btn btn-success'>登录</button> <button className='btn btn-danger'>取消</button> </Dialog> </main>, root);
2.基于继承COMPONENT类来创建组件
基于CREATE-ELEMENT把JSX转换为一个对象,当RENDER渲染这个对象的时候,遇到TYPE是一个函数或者类,不是直接创建元素,而是先把方法执行:
->如果是函数式声明的组件,就把它当做普通方法执行(方法中的THIS是UNDEFINED),把函数返回的JSX元素(也是解析后的对象)进行渲染
->如果是类声明式的组件,会把当前类NEW它执行,创建类的一个实例(当前本次调取的组件就是它的实例),执行CONSTRUCTOR之后,会执行THIS.RENDER(),把RENDER中返回的JSX拿过来渲染,所以“类声明式组件,必须有一个RENDER的方法,方法中需要返回一个JSX元素”
但是不管是哪一种方式,最后都会把解析的出来的PROPS属性对象作为实参传递给对应的函数或者类
import React from 'react'; import ReactDOM from 'react-dom'; function Sum(props) { // console.log(this);//=>undeifned return <div> 函数式声明 </div>; } class Dialog extends React.Component { constructor(props) {//=>props, context, updater super(props); //=>ES6中的EXTENDS继承,一但使用了CONSTRUCTOR,第一行位置必须设置SUPER执行: //相当于React.Component.call(this),也就是CALL继承,把父类私有的属性继承过来 //=>如果只写SUPER():虽然创建实例的时候把属性传递进来了,但是并没有传递父组件,也 //就是没有把属性挂载到实例上,使用THIS.PROPS获取的结果是UNDEFINED //=>如果SUPER(PROPS):在继承父类私有的时候,就把传递的属性挂载到了子类的实 //例上,CONSTRUCTOR中就可以使用THIS.PROPS了 //=>PROPS:当RENDER渲染并且把当前类执行创建实例的时候,会把之前JSX解析出来的PROPS //对象中的信息(可能有CHILDREN)传递给参数PROPS => “调取组件传递的属性” console.log(props); /!* * this.props:属性集合 * this.refs:REF集合(非受控组件中用到) * this.context:上下文 *!/ console.log(this.props); } render() { return <section> <h3>系统提示</h3> <div></div> </section>; } } ReactDOM.render(<div> 珠峰培训 <Dialog lx={2} con='哈哈哈'> <span>我是子元素</span> </Dialog> </div>, root);
class Dialog extends React.Component { constructor() { super(); /*即使在CONSTRUCTOR中不设置形参PROPS接收属性,执行SUPPER的时候也不传这个属性, //除了CONSTRUCTOR中不能直接使用THIS.PROPS,其它生命周期函数中都可以使用 //(也就是执行完成CONSTRUCTOR,REACT已经帮我们把传递的属性接收,并且挂载到实例上了)*/ } /*componentWillMount() { //=>第一次渲染之前 console.log(this.props); }*/ render() { return <section> <h3>系统提示</h3> <div></div> </section>; } } ReactDOM.render(<div> 珠峰培训 <Dialog lx={2} con='哈哈哈'> <span>我是子元素</span> </Dialog> </div>, root);
=========================================================================
[函数式]
1. 操作非常简单
2. 能实现的功能也很简单,只是简单的调取和返回JSX而已
[创建类式]
1. 操作相对复杂一些,但是也可以实现更为复杂的业务功能
2. 能够使用生命周期函数操作业务
3. 函数式可以理解为静态组件(组件中的内容调取的时候就已经固定了,很难在修改),而类这种方式,可以基于组件内部的状态来动态更新渲染的内容
4. ...
import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; class Dialog extends React.Component { /*THIS.PROPS是只读的,我们无法在方法中修改它的值,但是可以给其设置默认值或者设置一些规则 //(例如:设置是否是必须传递的以及传递值的类型等)*/ static defaultProps = { lx: '系统提示' }; /*PROP-TYPES是FACEBOOK公司开发的一个插件,基于这个插件我们可以给组件传递的属性设置规则 //(设置的规则不会影响页面的渲染,但是会在控制台抛出警告错误)*/ static propTypes = { // con: PropTypes.string //=>传递的内容需要是字符串 con: PropTypes.string.isRequired //=>不仅传递的内容是字符串,并且还必须传递 }; constructor(props) { super(props); } render() { //=>this.props.con = '嘿嘿嘿';//=>Cannot assign to read only property 'con' of object //组件中的属性是调取组件的时候(创建类实例的时候)传递给组件的信息,而这部分信息是“只读”的 //(只能获取不能修改) =>“组件的属性是只读的” let {lx, con} = this.props; return <section> <h3>{lx}</h3> <div>{con}</div> </section>; } componentDidMount() { console.log(this.props.lx); } } ReactDOM.render(<div> 珠峰培训 <Dialog con='哈哈哈'/> </div>, root);
class Parent { constructor(x, y) { //=>给实例设置私有的属性 this.x = x; this.y = y; } //=>Parent.prototype render() { //=>this.render() } //=>把Parent当做一个普通的对象,设置的私有属性方法,和实例没关系 static ajax() { //=>Parent.ajax() } } Parent.prototype.AA = 12; //=>ES6创建类的大括号中只能写方法(而且不能是箭头函数),不能设置属性,属性需要自己额外拿出来设置 Parent.BB = 12; //=>把它作为对象设置的私有属性也只能拿到外面设置 //==================== class Children extends Parent { constructor() { super(10, 20);//=>Parent.constructor.call(this,10,20) /*Parent.constructor(x, y) { //=>给实例设置私有的属性 this.x = x; this.y = y; }*/ // this.x =10 // this.y =20 // this.ajax(); //=>this.ajax is not a function //子类只能继承父类原型上的属性和方法和父类实例私有的属性和方法,对于父类作为普通对象设置的 //私有属性和方法是无法继承的 } render() { } } console.dir(new Children()); /* * { * x:10, * y:20, * __proto__:Children.prototype * render * __proto__:Parent.prototype * render * AA:12 * __proto__:Object.prototype * } */
import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; class Dialog extends React.Component { //=>这样是不符合ES6语法规范的,但是WEBPACK打包编译的时候会把它转换为Dialog.defaultProps这种复合规范的语法 static defaultProps = { lx: '系统提示' }; static propTypes = { con: PropTypes.string.isRequired }; //=>类似的这样写也是可以的(不是合法的ES6语法,但是WEBPACK会把它编译 =>BABEL-PRESET-REACT) /*AA = 12; fn = () => { console.log(1); }*/ constructor(props) { super(props); } render() { //=>this.props.con = '嘿嘿嘿';//=>Cannot assign to read only property 'con' of object 组件中的属性是调取组件的时候(创建类实例的时候)传递给组件的信息,而这部分信息是“只读”的(只能获取不能修改) =>“组件的属性是只读的” let {lx, con} = this.props; return <section> <h3>{lx}</h3> <div>{con}</div> </section>; } } ReactDOM.render(<div> 珠峰培训 <Dialog con='哈哈哈'/> </div>, root);
* REACT中的组件有两个非常重要的概念:
* 1. 组件的属性:[只读]调取组件的时候传递进来的信息
* 2. 组件的状态:[读写]自己在组件中设定和规划的(只有类声明式组件才有状态的管控,函数式组件声明不存在状态的管理)
* =>组件状态类似于VUE中的数据驱动:我们数据绑定的时候是基于状态值绑定,当修改组件状态后,对应的JSX元素也会跟着重新渲染(差异渲染:只把数据改变的部分重新渲染,基于DOM-DIFF算法完成)
* =>当代前端框架最重要的核心思想就是:“数据操控视图(视图影响数据)”,让我们告别JQ手动操作DOM的时代,我们以后只需要改变数据,框架会帮我们重新渲染视图,从而减少直接操作DOM(提高性能,也有助于开发效率)
* 所谓函数式组件是静态组件:和执行普通函数一样,调取一次组件,就把组件中的内容获取到,插入到页面中,如果不重新调取组件,显示的内容是不会发生任何改变的
*
* 真实项目只有调取组件,组件中的内容不会再次改变的情况下,我们才可能使用函数式组件
function Clock() { return <section> <h3>当前北京时间为:</h3> <div style={{color: 'red', fontWeight: 'bold'}}> {new Date().toLocaleString()} </div> </section>; } setInterval(() => { //=>每间隔1000MS重新调取组件,然后渲染到页面中 ReactDOM.render(<Clock/>, root); }, 1000);
import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; class Clock extends React.Component { constructor() { super(); //=>初始化组件的状态(都是对象类型的):要求我们在CONSTRUCTOR中,需要把后期使用的 //状态信息全部初始化一下(约定俗称的语法规范) this.state = { time: new Date().toLocaleString() }; } async componentDidMount() { //=>REACT生命周期函数之一:第一次组件渲染完成后触发(我们在这里只需要间隔1000MS //把STATE状态中的TIME数据改变,这样REACT会自动帮我们把组件中的部分内容进行重新的渲染) setInterval(() => { //=>REACT中虽然下面方式可以修改状态,但是并不会通知REACT重新渲染页面 // this.state.time = new Date().toLocaleString(); // console.log(this.state.time); /* * 修改组件的状态 * 1. 修改部分状态:会用我们传递的对象和初始化的STATE进行匹配,只把我们传递 //的属性进行修改,没有传递的依然保留原始的状态信息(部分状态修改) * 2. 当状态修改完成,会通知REACT把组件JSX中的部分元素重新进行渲染 */ this.setState({ time: new Date().toLocaleString() }, () => { //=>当通知REACT把需要重新渲染的JSX元素渲染完成后,执行的回调操作 //(类似于生命周期函数中的componentDidUpdate,项目中一般使用钩子 //函数而不是这个回调) //=>设置回调函数的原因:SET-STATE是异步操作 }); }, 1000); /* //=>把异步的SET-SATE修改为同步(没啥卵用) await this.setState({ time: new Date().toLocaleString() }); console.log(1); */ } render() { console.log(2); return <section> <h3>当前北京时间为:</h3> <div style={{color: 'red', fontWeight: 'bold'}}> {/*获取组件的状态信息*/} {this.state.time} </div> </section>; } } ReactDOM.render(<Clock/>, root);