1. react- 基础介绍

理念:如无必要,勿增实体
Entities should not be multiplied unnecessarily。这句话出自“奥卡姆剃刀原理”。

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框 架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套 东西很好用,就在2013年5月开源了。

vue框架是官方维护的,而react是社区维护的。

img

2. 视图层的 开发模式函数式编程

React 并不是完整的 MVC/MVVM 框架,它专注于提供清晰、简洁的 View(视图) 层解决方案。

用于构建用户界面的 JavaScript 库(Facebook-2013年5月份)-基于view层——>其项目负责人说:react不能称之为框架,而是一个库,创建组件打通与ui之间的通道,其他的需要自己根据需要安装周边插件。——仅仅是一个视图层(view层)

img

img

img

img

语义化标签

3. jsx 语法 与 组件

JSX 将 HTML 语法直接加入到 JavaScript 代码中,再通过翻译器转换到纯 JavaScript 后由浏览器执 行。在实际开发中,JSX 在产品打包阶段都已经编译成纯 JavaScript,不会带来任何副作用,反而会让代 码更加直观并易于维护。 编译过程由Babel 的 JSX 编译器实现。

https://reactjs.org/docs/hello-world.html

0.理解

jsx就是javascript+xml,允许在js里直接书写HTML代码,遇到<>就是html,遇到{}就是js变量或表达式。所以虚拟DOM就是html书写在js,js是存于内存中的,目的提升性能。

diff:key属性;fiber:每当使用setState修改数据,react会得到新的虚拟DOM,对比之后,找到变更的部分.放进队列里面.积攒到一定时批量更新,比diff算法更能提升性能。

0. 底层react核心库和webpack的loader的作用

jsx ==> js + xml(xml标签语法,不应该加引号,加引号反而只会当成字符串渲染)

jsx中的注释: {/*注释的内容*/}

  1. // 使用jsx,必须引入react核心库
  2. ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));
  3. // webpack通过babel这个loader转化为React.creatElement('')

是一种语法糖
jsx是javascript扩展的意思
相当于 js+xml
jsx 不是必须的,但是用jsx可以提高开发效率
jsx的原理就是 React.createElement(tag,{attrs},content)

0. 注意事项

(1) 组件首字母是大写, 会被认为是自定义组件; 首字母是小写, 会被认为是 原生dom 节点

(2) 组件最外层需要被一个标签包裹-即一个根节点,不能有兄弟节点

(3) return (加上小括号,可以回车) - 原生js规定

(4) 组件可以嵌套

(5) 函数式写法和class 写法 (无状态组件的编写方式)

(6) 注释的写法 — {//单行} {/多行/}

  1. {//单行注释} {/*多行注释*/}

(7) 元素属性

  1. value 改为defaultValue, checked 改为 defaultChecked
  2. a. class(元素类属性) ==> className (在jsclass是关键字), for(表单label的属性) ==> htmlFor(在js中是for循环关键字)

16版本之前,不支持class写法,无法编译,必须写成className

16版本之后,可以写class,但是控制台会报警告,这样每次回根据语义判断是元素类属性还是构造函数类,为了提高效率以及美观。建议写成className

  1. b. 行内样式(facebook 推荐)-不支持htmlstyle行内样式,而是对象写法,又一次证明是jsx语法。注意font-size 的写法(驼峰)
  1. {/* for 属性规定 label 与哪个表单元素绑定。*/}
  2. 显式的联系:
  3. <label htmlFor="SSN">Social Security Number:</label>
  4. <input type="text" name="SocSecNum" id="SSN" />
  5. 隐式的联系:
  6. <label>Date of Birth: <input type="text" name="DofB" /></label>
  7. // 类名来确定样式
  8. import './css/index.css'
  9. <div className="类名"></div>
  10. // 行内样式
  11. <div style={obj}>对象的名字</div>
  12. <div style={ {...obj} }>对象的解构</div>
  13. <div style={ {fontSize: '10px'} }>对象的keyvalue</div>

(8) 事件

  1. a. 箭头函数 b. bind改变this指向 c. 与普通事件的区别

1. 遍历列表-map映射并设置key值

状态的更新 - useState

react遍历列表要指定key值,并注意标签的语义化(即ul,li)。可以使用map(推荐map;或者其他方法)
return后面要紧跟字符,若需回车换行,则包在括号里面

render每次只进行一次渲染,应该将render封装进一个函数,在数据第一次加载和数据更新时多次调用;或者使用this.forceUpdate()强制更/刷新;再或者使用官方推荐的setState修改状态后数据进行重新更新(先删除后新增)

  1. function showList(array, tag = "list") {
  2. return (
  3. <ul>{
  4. array.map((item, index) => {
  5. return (
  6. <li
  7. key={tag + index}
  8. className={currentIndex == index ? 'color' : ''}
  9. onClick={() => {
  10. currentIndex = index;
  11. render();
  12. }}
  13. >{item}</li>
  14. );
  15. })
  16. }</ul>
  17. );
  18. }

数组的find和findIndex方法
  1. let arr=[1,2,3,4,5]
  2. arr.findIndex(item=>item===3) //2 返回下标
  3. arr.find(item=>item===3) //3 返回数组的元素
  4. arr.findIndex(item=>item===6) //-1 没找到,则下标返回-1
  5. arr.find(item=>item===6) //undefined 没找到

2. 遍历对象

1)、Object.keys(obj) 2)、Object.values(obj)

方法是借助Object.keys(obj)的到一个包含所有key值的数组,遍历数组从而实现遍历对象

3. ref(标签或组件)

给标签设置ref, ref可以获取到应用的真实dom。给组件设置ref, ref可以获取到组件对象

ref标识节点的两种方式

  1. ref=”字符串”
  2. ref={(node)=>this.属性=node}//this.属性,可拿到当前的dom节点
  3. createRef()创建一个节点,一般赋值给state里的属性ref={this.state.节点名}

用法:this.refs.用ref标识的节点或挂载到statethis.state.节点名.current

  1. <div ref="节点名字">第一种: 不推荐</div>
  2. <div ref={(node)=>this.属性=node}>第二种: 绑定在this或this.state上</div>
  3. <div ref={(node)=>this.state.属性=node}>第二种:</div>
  4. // 第三种
  5. this.state={ref变量: createRef()}
  6. <node ref={this.state.变量} />
  7. 引用这个节点: this.state.变量.current 就可以引用这个节点了

注意: 不推荐的一种是把节点绑定到this.refs上;而官网推荐方法是直接把节点挂载到this。

4. state状态(异步操作)

不要再render内执行setState

react会首先创建虚拟DOM并diif对比虚拟DOM,再在setState在适当的时侯修改状态; 而你直接修改state的状态,就没有走这个过程。所以setState是异步的。

cb是在DOM树更新完成之后才会触发

更改state的值(不能直接修改,直接改就会视图不同步)。因此,解决办法是:用setState更改,改值会自动触发render<所以不需要调用render这种低级操作>。也不是vue的数据劫持。

用法:一般先复制备份,再修改备份。不能直接修改状态。

1.常用写法
  1. // 第一种:参数是对象(修改的内容)和cb(类似于vue的nextTick)
  2. //this.setState({key:新的value});//用在能修改的地方
  3. this.setState({n:this.state.n+1},()=>{console.log('最新的值',n);});//在回调函数里去拿
  4. console.log('修改之前的值,说明是异步的',n);
  5. //第二种写法:第一个回调函数(参数是上一个state和props)和cb。记得return
  6. // 特点是: 第一个参数是上一次的state
  7. this.setState((prevState,props)=>{
  8. return {key:prevState.key+1}
  9. },cb);
  10. //一行大括号和return可省略。但是如果返回对象,大括号又会被当做函数体,所以对象外层加小括号
  11. this.setState((prevState,props)=>({key:prevState.key+1}),cb);

2.同一事件loop内连续执行多次

注意: 连续执行多次this.setState({}),对象会合并,并且后面的会覆盖前面的,因此只执行最后一次。

  1. // 第一种: 合并。因为是异步的,所以两次都是在n=1的基础上去加1,所以看起来只执行一次。不会等待
  2. this.setState({n:this.state.n+1})
  3. this.setState({n:this.state.n+1})
  4. // 第二种: 同一事件loop内连续执行多次会放入对列中依次执行。因为preState会等待上一次执行完毕后在执行下一个
  5. this.setState((prevState,props)=>({key:prevState.key+1}),cb);
  6. this.setState((prevState,props)=>({key:prevState.key+1}),cb);//等待
  7. // 第一种的回调函数内在执行setState亦可以实现队列
  8. this.setState({n:this.state.n+1},()=>{
  9. this.setState({n:this.state.n+1})
  10. })

浅赋值(引用赋值): 赋值的只是key,而不是地址,两者息息相关。因为内存中的地址相同,一改全改。最常见的直接等号赋值。

深赋值: 现在内存新建一个地址空间,再把值拿过来放进去,之后再无瓜葛。例如解构赋值、concat、slice、Object.assgin等等,它们的特点是不改变原数据,生成一个新地址的数据。

对象的浅拷贝(仅拷贝最外层的数据),扩展运算符 , Object.assign

var obj = {…obj2}
var obj = Object.assign({},obj2)

深赋值 === 浅拷贝?????

5. 事件处理函数onXxx

写在标签上为什么在React可以?

因为1.如果组件被卸载,事件也会被卸载,不会导致内存泄漏(溢出)

  1. 2.所有的事件监听都是挂载到顶层对象上(委托技术,提高性能)

但是定时器绑定在组件上不清理,会导致内存的泄漏

类组件事件处理函数(其实就是方法):

  1. **注意:**事件不绑定this,在事件内部拿不到this。在原生js指向DOM节点,而jsx解析器在jsxxml节点中绑定事件会丢失this-即undefined

5.0 this的概念、箭头
  1. this指向是谁在用就指向谁
  2. 箭头函数有保留this指向的作用,永远是指向函数定义时的外部作用域,而与函数调用的环境无关。

5.1 改变this指向的几种方法
方法 修改this执行&传参 解释
call a.call(b,1,2) 第一个参数表示要把this指向的新目标,第二个之后的参数其实相当于传参,参数以,隔开 (性能较apply略好)
表示要把a函数的this指向修改为b的this指向,并且运行a函数,传进去的参数是(1,2)
apply a.apply(b,[1,2]) 第一个参数同上,第二个参数接受一个数组,里面也是传参,只是以数组的方式,相当于arguments
表示要把a函数的this指向修改为b的this指向,并且运行a函数,传进去的参数是(1,2) 注意 :即使只有一个参数的话,也要是数组的形式
a.call(b,1,2)
仅仅绑定this,返回的还是函数,不会自动执行,调用才会执行
bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。 注意:多次bind无效,按第一次算

总结: (1)apply 、call 、bind 三者都是用来改变函数的this对象的指向的;
   (2)apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
   (3)pply 、 call 两者都可以利用后续参数传参; 但是传参的方式不一样,apply是数组,call是正常传参形式
   (4)bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

代码解读:

  1. // call 的传参和apply的传参
  2. function say(arg1,arg2){
  3. console.log(this.name,arg1,arg2);
  4. };
  5. var obj = {
  6. name : 'tom',
  7. say : function(){
  8. console.log(this.name);
  9. }
  10. }
  11. say.call(obj,'one','two');//tom one two
  12. say.spply(obj,['one','two']);//tom one two 效果一样
  13. // bind
  14. var foo = {
  15. bar : 1,
  16. eventBind: function(){
  17. $('.someClass').on('click',function(event) {
  18. /* Act on the event */
  19. console.log(this.bar); //1
  20. }.bind(this));//这里的this是eventBind的this,即指向的是foo
  21. }
  22. }

5.2 new和return涉及改变this指向的概念

5.21 new

  1. // (1)原理
  2. new Animal('cat') = {//类似这样
  3. var obj = {};//先定义一个空对象
  4. obj.__proto__ = Animal.prototype;//把 obj 的__proto__ 指向构造函数 Animal 的原型对象 prototype,此时便建立了 obj 对象的原型链:obj->Animal.prototype->Object.prototype->null
  5. var result = Animal.call(obj,"cat");//改变this指向,从Animal改变到obj上
  6. return typeof result === 'object'? result : obj; //返回
  7. }
  8. // (2)用法
  9. function Fn(){
  10. this.user = "追梦子";
  11. }
  12. var a = new Fn();//this指向a
  13. console.log(a.user); //追梦子

5.22 return

  1. // 在构造函数的时候,使用return进行返回一个Object的时候,当去new一个实例对象的时候,会将this指向改变为return的Object;
  2. function Fn(){
  3. this.user = '追梦子';
  4. return {
  5. "user" : "111"
  6. };
  7. }
  8. var a = new Fn;
  9. console.log(a.user); //111

5.3 react绑定this方法
  1. 要绑定this<this.say.bind(this)> 或者 函数写成箭头函数形式(即this指向箭头函数的外层-类的实例)

    1. class Person {
    2. constructor() {
    3. }
    4. say() {
    5. console.log(this);
    6. console.log('say方法');
    7. }
    8. demo() {
    9. this.say();//Person {} 和 say方法
    10. let say1 = this.say;//函数去赋值,导致say的this丢失。
    11. //切换到react中onClick={this.func}也是赋值丢失this;解决办法就是加强this指向
    12. say1();//undefined 和 say方法
    13. }
    14. }
    15. let p = new Person();
    16. p.demo();
  1. 如果使用绑定this方法,建议尽量在constructor[抄袭的java,封装的语法糖]里绑定this,如this.say=this.say.bind(this),因为这样只会执行一次绑定,而直接写在onXxxx后面每次渲染都要绑定一次
    **利用bind加强指向 bind就是函数执行时this的指向(因为this指向xml节点时丢失了)

6. 事件绑定处理函数,如何进行传参?

==若没有参数或者不绑定this,别加();==加了就自动执行,把函数的返回值赋值给onXxx。

  1. <button onClick={()=>{console.log(111)}}>直接回调写逻辑</button>
  2. <button onClick={this.change.bind(this)}>bind绑定不传参</button>
  3. <button onClick={this.change.bind(this,实参)}>bind绑定并传参</button>
  4. <button onClick={this.change2(实参)}>箭头函数不传参</button>
  5. <button onClick={()=>{this.change2(箭头实参)}}>箭头函数传参</button>
  6. bind可以一并处理: 不需要写箭头函数,也可以传参

那么事件对象参数呢?onXxx的时候不需要写实参
若不传参时,直接可以通过形参e拿到;若传了参数,形参的最后一项默认是事件对象: change(e) 、change(参数1,参数2,...,e)

7.onChange在原生是失去焦点触发,但在react中对onChange重写,并且官方声明表单建议使用onChange。

8. jsx中渲染html字符串: dangerouslySetInnerHTML

<span dangerouslySetInnerHTML={{ __html: 含html的字符串}}></span>

如果不想要解析字符串,可以在return之前,进行判断或循环得到dom,然后直接放入return中显示

9.v-show和v-if

1.v-show

三目判断加className或者style属性

2.v-if

三目判断渲不渲染DOM。v-else就是switch来实现

  1. {
  2. this.state.isCreated?<div>渲染DOM</div>:null
  3. }

10.props - 见组件Note