ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 组件化和React 发现bug,解决bug的能力和效率 ### 组件化 ### 题目 1. 说一下对组件化的理解 2. JSX本质是什么 3. JSX和vdom的关系 4. 说一下setState的过程 5. 阐述对React和vue的认识 #### 用React todolist 命令: npm i create-react-app -g (sudo:mac和linux) create-react-app react-test cd react-test npm start ~~~ import React,{Component} from "react"; import logo from "./logo.svg"; import "./App.css" import Todo form ".components/todo/index.js" class App extends Component{ render(){ return ( <div className="App"> <header className="header"> <img src={logo}> </header> <Todo/> </div> ) } } ~~~ componets/todo/index.js ~~~ import React,{Component} from "react"; import List from "./list/index.js"; import Input from "./input/index.js" class Todo extends Component{ constructor(props){ super(props); this.state={ list: [], } } render(){ return( <div> <p>this is todo component</p> <Input addTitle={this.addTitle.bind(this)} /> <List data={this.state.list}/> </div> ) } addTitle(title){ const currentList = this.state.list; this.setState({ list: currentList.concat(title), }) } } export default Todo; ~~~ todo文件夹中建一个list文件夹,在添加index.js ~~~ import React,{Component} from "react"; class List extends Component{ constructor(props){ super(props) } render(){ const list = this.props.data; return ( <ul> { list.map((item,index)=>{ return <li key="index">{list}</li> }) } </ul> ) } } export default List; ~~~ react规定,所有的遍历里面都有key todo文件夹下建一个input文件夹,index.js ~~~ import React ,{Component} from "react"; class Input extends Component{ constructor(props){ super(props); this.state={ title:'' } } render(){ return( <div> <input value={this.state.title} onChange = {this.changeHandle.bind(this)} <button onClick={this.clickHandle.bind(this)}>Submit</button> </div> ) } changeHandle(e){ this.setState({ title:e.target.value }) clickHandle(){ const title = this.state.title; //把值拿到,把title添加进列表 const addTitle = this.props.addTitle; addTitle(title);//重点 this.setState({ title:'' }) } } } export default Input; ~~~ bind(this)//绑定当前组件的实例 react:必须用这种**构建方式**去做 vue:也可以这么做(组件化),但为了方便,简单,快速,突出重点,选择了一种简单的cdn引文件方式做,(初学者) 但是:**复杂的配置是为了解决问题,让后面的使用更加简单**看似复杂的东西,是为了解决后面更复杂的问题,为了让后面的使用更加简单。 react:看似写起来很麻烦,但讲起来特别简单,结构特别清晰,组件直接的引用逻辑上特别干练,这种方式来做大型的项目开发才是最好的方式(不一定是react,vue也可以用这种方式) react: 1. 数据和视图分离 数据保存在state中,视图在render方法中 2. 通过修改数据驱动视图变化,不是直接操作dom 3. react和vue的不同:react是组件化的,组件化的每个组件都支持上两条,vue整个就是mvvm的架构,两个特点就是在外部体现的,但是也是支持组件的,vue是先有了mvvm特点之后,在mvvm的基础上把组件抽象出来而已。 4. 所以讲mvvm拿vue来讲,将组件化拿react讲,特点非常鲜明 ### 说一下对组件化的理解 (组件可以封装很多很多东西,然后外界都不用关心,想用的时候可以直接用,而且可以定义一次复用很多次)(面向对象:封装,复用,多态) 组件的封装就是为了给外界多次使用 **所谓组件化,就是把页面拆分成多个组件(component),每个组件依赖的css,JavaScript、模板、图片等资源放在一起开发和维护。组件是资源独立的,组件在系统内部可复用,组件和组件之间可以嵌套。** 1. 组件的封装 * 视图 * 数据 * 变化逻辑(数据驱动视图变化)(黑盒)(组件内部的变化逻辑,外界不需知道,) 以上三点调用者都可以不用知道,只需将符合要求的数据传过来就行 2. 组件的复用 复用:就需要像组件传一些东西(不同属性) * props传递 ### 问题解答 1. 组件的封装:封装视图,数据,变化逻辑 2. 组件的复用:props传递(达到复用的效果)、复用(封装一次,复用多次) ***** ### JSX本质是什么 react把jsx真正的推广开来,好用,简单,功能强大;语法糖 1. JSX语法 2. JSX解析成JS 3. 独立的标准(不仅仅是react的从属,可以用到其他库) 解答: 1. JSX语法 * html形式(所有的html标签,jsx都支持) * 引入js变量和表达式(大括号中,可以引入变量,表达式,注释等) jsx中的** 注释**{/* */} :原因:jsx中可以在**大括号中加入js变量或表达式** * if.. else...判断 ~~~ { show?<img/>:'' } } ~~~ * 循环 ~~~ { list.map((item,index)=>{ return <li key={index}>{item}</li> }) ~~~ * style和className //第一个大括号用来表示是js表达式,第二个大括号表示对象格式 //class是保留字 ~~~ const styleConfig = { fontSize:'40px', color:'blue' } return( <div className="container"> <p style={styleConfig}></p> //第一个大括号用来表示是js表达式,第二个大括号表示对象格式 <p style={{fontSize:'40px',color:‘blue’}}></p> </div> ) ~~~ * 事件 ~~~ import React,{Component} from "react"; import logo from "./logo.svg"; import "./App.css" import Todo form ".components/todo/index.js" class App extends Component{ render(){ const show = true; return ( <div className="App"> <header className="header"> <img src={logo}> </header> {/* <Todo/> *}/ { show?<img/>:''} } </div> ) } } ~~~ * JSX语法根本无法被浏览器所解析(vue的指令也无法被浏览器解析,只是一种表述方式) * 那么他是如何在浏览器中运行?(像vue的模板一样,最终转为js来运行的)(依赖 import react) ### JSX解析 -本质:解析成js代码 JSX是一个语法糖,真正的是要解析成js代码来执行,React.createElement的用法(用来) ~~~ /* jsx代码*/ var profile = <div> <img src="avatar.png" className="profile" /> <h3>{[user.fistName,user.lastName].join(' ')}</h3> </div> //解析结果 var profile = React.createElement('div',null, React.createElement("img",{src:"avatar.png",className:"profile"}), React.createElement("h3",null,[user.fistName,user.lastName].join(' ')), ) //标签名 属性 子元素 //React.createElement参数说明 React.createElement('div',{id:'div'},child1,child2,child2); React.createElement('div',{id:'div'},[...]); ~~~ **vdom中h()函数,返回vnode,vue中_c(),返回vnode,React.createElement类似(标签、属性、子元素)** ### JSX解析 1. JSX其实是语法糖(写是这么写,实际不是这么回事) 2. 开发环境下会将JSX编译成js代码(和vue2.0相似,模板是在开发环境下写的,在上线之前要变异成render函数),真正上线的是js代码 3. JSX的写法大大降低了学习成本和编码工作量 4. 同时,JSX也会增加debug成本 ### JSX 独立的标准(不从属于react) 1. JSX是React引入的,但不是React独有的 2. React已经将它作为一个独立标准开放,其他项目也可用 3. React.createElement(核心函数,将jsx转为js)是可以自定义修改的 4. 说明:(可信,可用)本身功能已经完备;和其他标准兼容和扩展性没问题 5. 前端有什么标准?? ~~~ npm init npm i babel -g npm install --save-dev babel-plugin-transform-react-jsx ~~~ .babelrc ~~~ {"plugins":["transform-react-jsx"]} ~~~ babel -- plugins transform-react-jsx demo.jsx 解析之后 ~~~ class Input extends Component{ render(){ return React.createElement( "div", null, React.createElement("input",{value:this.state.title,onChange:thies.changeHandle.bind(this)}), React,createElement("button",{onClick:this.clickHandle.bind(this)},"submit") ) } } ~~~ 修改React.createElement 改成h函数 ~~~ /* @jsx h */ ~~~ ### 问题解答(jsx本质) 1. jsx语法(标签,js表达式,判断,循环,事件绑定) 2. jsx是语法糖,需被解析成js才能运行(本质)(vue的模板也需要被解析成js代码才能运行;jsx这个语法糖最终被解析成js的形式:React.createElement核心函数,标签属性子元素,联系到vdom的h函数) 3. JSX是独立的标准,可被其他项目使用(preact) ***** ### jsx和vdom的关系 vdom是伴随react而来的 1. 分析:为何需要vdom 2. React.createElement和h 3. 何时patch? 4. 自定义组件的解析 #### 为何需要vdom 1. vdom是react初次推广开来的,结合jsx 2. jsx就是模板,最终要渲染成html(类似于vue模板必须渲染成html)(必须使用vdom的原因,所有渲染成html都是通过vdom执行的,没有jsx直接操作dom的说法) 3. 初次渲染+修改state后的re-render 4. 正好符合vdom的应用场景(表格,数据变化,表格变化) #### 再次回顾vdom 1. 介绍snabbdom 2. 核心api:h函数和patch函数 #### React.createElement和h函数 都是返回vnode ~~~ var profile = React.createElement('div',null, React.createElement('img',{src:'avatar.png',className:'profile'}), React.createElement('h3',null,[]) ) var vnode = h('div#conatainer.two.classes',{on:{click:someFun}},[ h('span',{style:{fontWeight:'bold'}},'this is bold'), h('a',{props:{href:'./foo'}},"i will take your places") ]) ~~~ vnode的样式 ~~~ { tag:'div', attrs:{}, children:[ {tag:'img',attrs:{src:"avatar.png",className:"profile"},children:[]}, {tag:'h3',attrs:{},children:[]}, ] } ~~~ #### 何时patch 1. 初次渲染--**ReactDOM.render(<App/>,container)**;(vnode:jsx要转换成js代码的执行,这个js代码执行时要返回vnode,<App/>返回jsx结构,就要转换为js代码执行,返回vnode) 2. 会触发patch(container,vnode) 3. re-render--setState 4. 会触发patch(vnode,newVnode)(对比,渲染时vdom干的事) ~~~ //patch(container,vnode) ReactDOM.render(<App />,document.getElementById('root'));//触发第一个用法 //patch(vnode,newVnode) this.setState({ list:currentList.concat(title) }) ~~~ #### 自定义组件的处理(<App /> <Input/>..) ~~~ <div> <p>this is demo</p> <Input dataTitle={this.addTitle.bind(this)} /> <List data={this.state.list}> </div> ~~~ ~~~ function render(){ return React.createElement( "div", null, React.createElement( 'p', null, 'this is demo' ), React.createElement(Input,{addTitle:this.addTitle.bind(this)}), React.createElement(List,{data:this.state.list}), ) } ~~~ return React.createElement(** "div"**, null,‘’) React.createElement(**Input**,{addTitle:this.addTitle.bind(this)}), React.createElement(**List**,{data:this.state.list}), 创建普通标签,标签名是字符串,**创建自定义组件,要传入自定义组件的构造函数**(Input,List) #### 自定义组件的解析 1. 'div',直接渲染<div>即可,vdom可以做到 2. Input和List,是自定义组件(class),**vdom默认不认识** 3. 因此Input和List定义的时候**必须声明render函数** 4. 根据props初始化实例,然后执行实例的render'函数 5. render函数返回的还是vnode对象(render函数是jsx,转换成js后返回vnode) ~~~ var list = new List({data:this.state.list}); var vnode = list.render(); ~~~ #### 示例演示 jsx ==> js ==> vnode ==> html ~~~ var app = <App />; ~~~ ~~~ React.createElement(App,null)//App是一个构造函数 var app = new App(); return app.render(); ~~~ App ~~~ class App extends Component{ render(){ return(<div><Todo/></div>) } } ~~~ ~~~ React.createElement("div",null,React.createElement(Todo,null)); var todo = new Todo(); todo.render(); ~~~ Todo ~~~ <div> <Input dataTitle={this.addTitle.bind(this)} /> <List data={this.state.list}> </div> ~~~ ~~~ React.createElement('div',null, React.createElement(Input,{dataTitle: this.dataTitle.bind(this)}), React.createElement(List,{data: this.state.list}), ) var input = new Input({dataTitle: this.dataTitle.bind(this)); input.render(); var list = new List({data: this.state.list}) list.render(); ~~~ ~~~ <ul> { list.map((item,index)=>{ return <li key="index">{list}</li> }) } </ul> ~~~ ~~~ React.createElement('ul',null,list.map((item,index)=>{ return React.createElement('li',{key: index},item) })) ~~~ 此时没有任何自定义组件了 不管组件嵌套多少层,不管从最外层到最里层要经过多少层render函数的调用,最终所有自定义的组件,都是靠html原生的标签支撑的,从一层一层的render函数,最终将自定义的组件转换为最底层的html标签,渲染页面 #### 问题解答( jsx和vdom的关系) 1. 为何需要vdom:jsx(语法糖)需要(最终)渲染成html(需要vdom,因为jsx需要转换成js代码),数据驱动视图(js不可能亲自操作dom,需要中间层vdom来操作渲染vdom) 2. React.createElement和h,都生成vnode(不一样:h函数第一个参数是标签名称。React.createElement的第一个参数两种:html标签名称,自定义组件构造函数的名称) 3. 何时patch:ReactDom.render()(初次渲染)和setState(re-render)(再次渲染) 4. 自定义组件的解析:初始化实例,然后执行render ### 说一下React setState的过程(异步) 不参与开发,只能了解大概,说到重点就行 1. setState的异步 2. vue修改属性也是异步 3. setState的过程 #### setState的异步 ~~~ addTitle(title){ const currentList = this.state.list; console.log(this.state.list);//['a','b'] this.setState({ list:currentList.concat(list);//'c' }) console.log(this.state.list);//['a','b'] } ~~~ #### setState为何需要异步? 1. 可能会一次执行多次setState 2. 你无法规定,限制用户如何使用setState 3. 没必要每次setState都重新渲染,考虑性能(dom操作非常昂贵的) 4. 即便是每次重新渲染,用户也看不到中间的效果(js的执行和dom渲染时单线程的,将a修改1,2,3,4,5,6,7,8,9,10,js修改过程中,dom渲染时卡顿的,用户看不到过程) 5. 只看到最后的结果即可 #### vue修改属性也是异步的 vue和react两个相互竞争,功能类似的框架,vue修改属性和react的setState也是功能基本相似的,两者功能基本一样 1. 效果、原因和setState一样 2. 对比记忆,印象深刻 3. 复习vue的渲染流程 #### vue的整个实现流程 1. 解析模板成render函数 2. 响应式开始监听 3. 首次渲染,显示页面,且绑定依赖 4. data属性变化,触发rerender * 修改属性,被响应式的set监听到(没有异步) * **set中执行updateComponent(异步)** * updateComponent重新执行vm.render() * 生成vnode和preVnode,通过patch进行对比 * 渲染到html中 #### setState的过程 1. 每个组件实例,都要renderComponent方法(expends Component,继承的Component中的方法) 2. 执行renderComponent 会**重新执行实例的render**,(重新按照最新的数据,生成最新的vnode) 3. render函数返回newVnode,然后拿到preVnode 4. 执行patch(preVnode,newVnode) setState后面可加回调函数, ~~~ addTitle(title){ const currentList = this.state.list; console.log(this.state.list);//['a','b'] this.setState({ list:currentList.concat(list);//'c' },()=>{ console.log(this.state.list);//['a','b','c'] //不用手动调用 this.renderCompoent(); }) console.log(this.state.list);//['a','b'] } ~~~ ~~~ class Component{ constructor(props){}, //模拟 renderComponent(){ const prevVnode = this._vnode; const newVnode = this.render(); patch(prevVnode , newVnode ); this._vnode = newVnode; } } ~~~ #### 问题解答(setState) 1. setState 异步:效果,原因(提供效率,汇集只渲染一次) 2. vue修改属性也是异步:效果,原因(位置:修改后set监听到,执行updateComponent时)(避免用户重复修改数据,页面不能每次修改就渲染一次,通过异步汇总结果) 3. setState的过程,最终走到patch(preVnode,newVnode) ## 总结 看穿现象,看到本质 1. 说一下对组件化的理解(组件的封装,组件的复用) 2. jsx的本质(jsx语法,语法糖,解析成js才能运行,vue的模板也需要解析成js,独立标准,其他项目也能用) 3. jsx和dom的关系(为何使用vdom,React.createelement和h都生成vdom,何时patch:ReactDom.render和setState,自定义组件的解析:实例化,render) 4. 说一下setState的过程(异步,vue修改属性也是异步,setState的过程,最终走到patch) 5. 阐述自己对React和vue的认识 ## React和vue的对比 技术选型没有绝对的对与错,考虑的因素也特别多(不是自己做,团队,小组,学习成本等),要有自己的主见 1. 两者的本质区别 * vue-本质是mvvm框架(最外层),由mvc发展而来 * React-本质是前端组件化框架(最外层),由后的组件化发展而来 * 但并不妨碍他们两者都能实现相同的功能 2. 看模板和组件化的区别 * vue-使用模板(最初由angular(4.或5了)提出,非常详细)(各种指令 vif vfor) * React - 使用jsx,(jsx已经标准化) * 模板语法上,更加倾向于JSX(标准化) * 模板分离上,更倾向于vue 模板的区别 ~~~ <div> <h1 v-if="ok">Yes</h1> <h1 v-else>No</h1> </div> <ul id="xx"> <li v-for="(item in items)">{{item.message}}</li> </ul> ~~~ ~~~ <div> {ok?<h1>Yes</h1>:<h1>No</h1>} </div> <ul id="xx"> { items.map((item,index)=>{ return <li key ={index}>{item.message}</li> }) } </ul> ~~~ vue:v-if v-else现学,v-if="ok" ok是js变量,用引号引起来,想html本身的属性 jsx:大括号里的能放js变量表达式,虽然模板和数据进行了分离,但模板和js混在一起,未分离 * 模板应该和js逻辑分离 * 回顾“开发封闭原则” 组件化的区别 * React本身就是组件化,没有组件化就不是React * vue也支持组件化,不过是在mvvm上的扩展 * vue组件化的文档(非常多,不是简单易用) * 对于组件化,更倾向于react,做的更彻底清晰 3. 两者的共同点 * 都支持组件化(不支持组件化,就像js不支持模块化,维护代码会多,很麻烦) * 都是数据驱动视图,数据和视图的分离 4. 总结问题的答案 * 国内使用,首推vue。文档更易读,易学,社区够大(基础知识不是那么扎实) * 如果团队水平较高,推荐使用React,组件化和jsx *