React基础(一)

1. 虚拟DOM

  1. DOM的本质

用js表示的UI元素

  1. DOM和虚拟DOM的区别

DOM是由浏览器中的JS提供的,所以我们只能使用浏览器提供的固定API操作DOM对象;虚拟DOM不是由浏览器提供的,而是人为手动模拟实现的,类似于浏览器中的DOM,但是有着本质的区别。

  1. 手动模拟DOM树的原理

使用JS创建一个对象,用这个对象,来模拟每一个DOM节点,然后在每个DOM节点中,又提供了类似于children这样的属性,来描述当前的DOM的子节点,这样,当DOM节点形成了嵌套关系,就模拟出了一颗DOM树。

  1. 为什么要实现虚拟DOM

为了实现DOM节点的高效更新

2. Diff算法

  1. tree diff

新旧DOM树,逐层对比的方式

  1. component diff

对比每一层的时候,组件之间的对比;当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置。

  1. element diff

元素级别的对比

  1. key

key这个属性,可以把页面上的DOM节点和虚拟DOM中的对象,做一层关联关系。

3. React项目的创建

  1. 安装react和react-dom

    npm install react react-dom -S
    
  2. react和react-dom导入

    // react:用来创建React组件、组件生命周期
    // react-dom:封装了和DOM操作相关的包,比如,要把组件渲染到页面上
    import React from 'react'
    import ReactDom from 'react-dom'
    
  3. 创建DOM虚拟节点

    // 参数1:字符串类型的参数,表示要创建的元素类型
    // 参数2:属性对象,表示创建的这个元素有哪些属性
    // 参数3:从第三个参数的位置开始,后面可以放好多的虚拟DOM对象,这些参数,表示当前元素的子节点
    var myH1 = React.createElement('h1', null, "这是一个H1标签");
    var myDiv = React.createElement('div', {title: 'div标签', id: 'mydiv'}, '这是一个div', myH1);
    
  4. 使用ReactDOM把元素渲染到页面指定的容器中

    // 参数1:要渲染的DOM元素
    // 参数2:要渲染到页面上的位置
    ReactDom.render(myDiv, document.getElementById('app'))
    

4. React基础语法

1. JSX语法

  • JSX语法的本质:以React.createElement的形式来实现的,并没有直接把用户写的HTML代码,渲染到页面上

  • 在JSX语法内部,书写JS代码,必须写到{}中

  • 编译引擎,在编译JSX代码的时候,如果遇到了’<‘那么就把它当作HTML代码去编译,如果遇到了’{}’,就把花括号内部的代码当作普通JS代码去编译

  • 在{}内部,可以写任何符合JS规范的代码

  • 在JSX中,若果要为元素添加’class’属性,那么必须写成’className’,因为’class’在ES6中是一个关键字。和’class’类似,label标签的for属性需要替换为htmlFor

  • 在JSX创建DOM的时候,所有的节点必须有唯一的根元素包裹

  • 注释也要放到花括号中

    // yarn add babel-preset-react --dev
    // 在.babelrc文件中配置
    // 使用jsx语法创建元素
    var sDivTitle = 'div title';
    var myDiv = <div title={sDivTitle + '!!!!'}>
    	jsx语法创建的div
    </div>
    

2. 组件

  • 在react中,构造函数就是一个最基本的组件

  • 如果想要把组件放到页面中,可以把 构造函数的名称,当作 组件的名称,以 HTML标签形式引入页面中即可

  • React在解析所有的标签的时候,是以标签的首字母来区分的,如果标签的首字母是小写,那么就按照 普通的 HTML 标签来解析,如果 首字母是大写,则按照 组件的形式去解析渲染。组件的首字母必须是大写

    function Hello(props) {
      // 在组件中,如果想要使用外部传递过来的数据,必须,显示的在 构造函数参数列表中,定义 props 属性来接收;
      // 通过 props 得到的任何数据都是只读的,不能从新赋值
      // props.name = '000'
      return <div>
        <h1>这是在Hello组件中定义的元素 --- {props.name}</h1>
      </div>
    }
    
    var person = {
      name: 'ls',
      age: 22,
      gender: '男',
      address: '北京'
    }
    
    // 这里 ...Obj  语法,是 ES6中的属性扩散, 表示,把这个对象上的所有属性,展开了,放到这个位置
    ReactDOM.render(<div>
     <Hello {...person}></Hello>
    </div>, document.getElementById('app'))
    

3. 类

  1. 例1

    //  class 实现面向对象的新形式
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.say = function () {
      console.log('ccblogs')
    }
    Person.info = 123
    
    var p1 = new Person('zs', 20)
    
    //  class 后面跟上类名, 类名后面,不需要加 () ,直接上 {}
    class Per {
      // 在每个class类内部,都有一个 constructor 构造器,如果没有显示定义构造器,那么类内部默认都有个看不见的 constructor
      // constructor 的作用,就好比 咱们之前的 function Person(){ }
      // 每当,使用 new 关键字,创建 class 类实例的时候,必然会优先调用 constructor 构造器
      // constructor(){}
      constructor(name, age) {
        this.name = name
        this.age = age
      }
    
      // 这是实例方法,必须通过 new 出来的对象调用
      say() {
        console.log('ok a ')
      }
    
      static info = 123
    
      static sayHello() {
        console.log('这是静态方法')
      }
    }
    
    var p2 = new Per('ccblogs1', 22)
    
    console.log(p2)
    console.log(Per.info)
    console.log(Per.sayHello())
    
  2. 例2

    class Person {
      constructor(name, age) {
        console.log(3)
        this.name = name
        this.age = age
      }
    
      say() {
        console.log('这是 Person中的 say 方法')
      }
      static info = 123
    }
    
    
    // 使用 extends 实现继承,extends 前面的是子类,后面的是父类
    class Chinese extends Person {
      constructor(name, age, color, language) {
        console.log(1)
        // 注意: 当使用 extends 关键字实现了继承, 子类的 constructor 构造函数中,必须显示调用 super() 方法,这个 super 表示父类中 constructor 的引用
        super(name, age)
        this.color = color
        this.language = language
        console.log(2)
      }
    }
    
    
    // var p1 = new Person('zs', 12)
    // console.log(p1)
    
    var c1 = new Chinese('张三', 22, 'yellow', '汉语')
    console.log(c1)
    // 父类中任何东西,子类都能继承到
    // c1.say();
    // console.log(Chinese.info)
    
    
    // 真正的面向对象语言是由 三部分组成: 封装、继承、多态
    // 多态 和 接口、虚拟方法有关
    
    
    class Animal {
      // 父类只定义了方法的名称,和作用,但是并没有具体的实现逻辑
      say() {
        // console.log('喵喵')
      }
    }
    
    class Cat extends Animal {
      // 当子类继承了父类之后,必然要继承父类中的方法,但是,发现say方法空有其壳,如果子类想要调用 say, 必须自己先实现这个方法,才能调用;
      say() {
        console.log('miaomiao')
      }
    }
    
    class Dog extends Animal {
      say() {
        console.log('wangwang')
      }
    }
    

4. scss

html, body{
  margin: 0;
  padding: 0;

  ul{
    list-style: none;
    padding: 0;
    margin: 0;
    
    li{
      font-size: 12px;
      line-height: 30px;
      padding-left: 10px;
    }
  }

  .box{
    width: 500px;
    height: 230px;
    background: url('../images/ccblogs.png');
    background-size: cover;
  }
}

5. 有状态组件与无状态组件

  • 使用 function 构造函数创建的组件,内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据;
  • 使用 class 关键字 创建的组件,内部,除了有 this.props 这个只读属性之外,还有一个 专门用于存放自己私有数据的 this.state 属性,这个 state 是可读可写的!
  • 使用 function 创建的组件,叫做【无状态组件】;使用 class 创建的组件,叫做【有状态组件】
  • 有状态组件和无状态组件,最本质的区别,就是有无 state 属性;同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数;
  • 如果一个组件需要存放自己的私有数据,或者需要在组件的不同阶段执行不同的业务逻辑,此时,非常适合用 class 创建出来的有状态组件;
  • 如果一个组件,只需要根据外界传递过来的 props,渲染固定的页面结构,此时,非常适合使用 function 创建出来的 无状态组件;(使用无状态组件的好处:由于剔除了组件的生命周期,所以,运行速度会相对快一点)
  1. 无状态组件

    import React from 'react'
    // 在 function 定义的组件中,如果想要使用 props,必须先定义,否则无法直接使用
    // 但是,在class定义的组件中,可以直接使用 this.props 来直接访问,不需要预先接收 props
    export default function Hello(props) {
      // console.log(props)
      return <p>haha --- {props.name}</p>
    }
    
  2. 有状态组件

    import React from 'react'
    
    // 使用 class 创建的类,通过 extends 关键字,继承了 React.Component 之后,这个类,就是一个组件的模板了
    // 如果想要引用这个组件,可以把 类的名称, 以标签形式,导入到 JSX 中使用
    export default class Hello2 extends React.Component {
      constructor(props) {
        // 注意: 如果使用 extends 实现了继承,那么在 constructor 的第一行,一定要显示调用一下 super()
        //  super() 表示父类的构造函数
        super(props)
        // 在 constructor 中,如果想要访问 props 属性,不能直接使用 this.props, 而是需要在 constructor 的构造器参数列表中,显示的定义 props 参数来接收,才能正常使用;
        // console.log(props)
    
        // 注意: 这是固定写法,this.state 表示 当前组件实例的私有数据对象,就好比 vue 中,组件实例身上的 data(){ return {} } 函数
        // 如果想要使用 组件中 state 上的数据,直接通过 this.state.*** 来访问即可
        this.state = {
          msg: '这是 Hello2 组件的私有msg数据',
          info: '***'
        }
      }
    
      // 在 class 实现的组件内部,必须定义一个 render 函数,表示渲染哪些虚拟DOM元素并展示出来
      render() {
    
        // 在 render 函数中,必须 return ,如果没有什么需要被return 的,则需要 return null
    
        // 虽然在 React dev tools 中,并没有显示说 class 组件中的 props 是只读的,但是,只要是组件的 props,都是只读的;
        // console.log(this.props)
    
        return <div>
          <h1>这是 使用 class 类创建的组件</h1>
          <h3>外界传递过来的数据是: {this.props.address} --- {this.props.info}</h3>
          <h5>{this.state.msg}</h5>
    
          {/* 1.1 在React中,如果想要为元素绑定事件,不能使用 网页中 传统的 onclick 事件,而是需要 使用 React 提供的  onClick */}
          {/* 1.2 也就是说:React中,提供的事件绑定机制,使用的 都是驼峰命名,同时,基本上,传统的 JS 事件,都被 React 重新定义了一下,改成了 驼峰命名 onMouseMove  */}
          {/* 2.1 在 React 提供的事件绑定机制中,事件的处理函数,必须直接给定一个 function,而不是给定一个 function 的名称 */}
          {/* 2.2 在为 React 事件绑定 处理函数的时候,需要通过 this.函数名, 来把 函数的引用交给 事件 */}
          <input type="button" value="修改 msg" id="btnChangeMsg" onClick={this.changeMsg} />
    
          <br />
    
        </div>
      }
    
      changeMsg = () => {
        // 注意: 在方法中,默认this 指向 undefined,并不是指向方法的调用者
        // 直接使用 this.state.msg = '123' 为 state 上的数据重新赋值,可以修改 state 中的数据值,但是,页面不会被更新;
        // 所以这种方式,React 不推荐,尽量少用;
    
        // 如果要为 this.state 上的数据重新赋值,那么,React 推荐使用 this.setState({配置对象}) 来重新为 state 赋值
        // 注意: this.setState 方法,只会重新覆盖那些显示定义的属性值,如果没有提供最全的属性,则没有提供的属性值,不会被覆盖;
        /* this.setState({
          msg: '123'
        }) */
    
        // this.setState 方法,也支持传递一个 function,如果传递的是 function,则在 function 内部,必须return 一个 对象;
        // 在 function 的参数中,支持传递两个参数,其中,第一个参数是 prevState,表示为修改之前的 老的 state 数据
        // 第二个参数,是 外界传递给当前组件的 props 数据
        this.setState(function (prevState, props) {
          // console.log(props)
          return {
            msg: '123'
          }
        }, function () {
          // 由于 this.setState 是异步执行的,所以,如果想要立即拿到最新的修改结果,最保险的方式, 在回调函数中去操作最新的数据
          console.log(this.state.msg)
        })
        // 经过测试发现, this.setState 在调用的时候,内部是异步执行的,所以,当立即调用完 this.setState 后,输出 state 值可能是旧的
        // console.log(this.state.msg)
      }
    }
    

6. 循环

import React from 'react'

// 导入当前组件需要的子组件
import CommentItem from './CommentItem.jsx'

// 评论列表组件
export default class CommentList extends React.Component {
  constructor(props) {
    super(props)

    // 定义当前评论列表组件的 私有数据
    this.state = {
      cmts: [
        { user: '张三', content: '哈哈,沙发' },
        { user: '张三2', content: '哈哈,板凳' },
        { user: '张三3', content: '哈哈,凉席' },
        { user: '张三4', content: '哈哈,砖头' },
        { user: '张三5', content: '哈哈,楼下山炮' }
      ]
    }
  }

  render() {
    //#region 循环 评论列表的方式1,比较low,要把 JSX 和 JS 语法结合起来使用
    /* var arr = []
    this.state.cmts.forEach(item => {
      arr.push(<h1>{item.user}</h1>)
    }) */
    //#endregion

    return <div>
      <h1 className="title">评论列表案例</h1>
      {/* 我们可以直接在 JSX 语法内部,使用 数组的 map 函数,来遍历数组的每一项,并使用 map 返回操作后的最新的数组 */}
      {this.state.cmts.map((item, i) => {
        // return <CommentItem user={item.user} content={item.content} key={i}></CommentItem>
        return <CommentItem {...item} key={i}></CommentItem>
      })}
    </div>
  }

7. 样式Style

  1. 模块化样式

    .box{
      border: 1px solid #ccc;
      padding-left: 15px;
      box-shadow: 0 0 6px #ccc;
      margin: 10px 0;
    }
    /* 注意:当启用 CSS 模块化之后,这里所有的类名,都是私有的,如果想要把类名设置成全局的一个类,可以把这个类名,用 :global() 给包裹起来 */
    /* 当使用 :global() 设置了全局的 类样式之后,这个类不会被重命名 */
    /* 只有私有的类才会被重命名 */
    :global(.title){
      color:red;
      text-align: center;
    }
    .title{
      color: green;
      font-size: 16px;
    }
    
    .body{
      font-size: 14px;
      color:red;
    }
    
  2. 样式对象

    // 导入一个 样式的对象
    export default {
      boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 },
      titleStyle: { fontSize: 16, color: "purple" },
      bodyStyle: { fontSize: 14, color: "red" }
    }
    
  3. 样式导入

    import React from 'react'
    // 在使用 import 的时候,import 只能放到模块的 开头位置
    import inlineStyles from './cmtItemStyles.js'
    
    // 这种直接 import '../路径标识符' 的 CSS 导入形式,并不是模块化的CSS
    // import '../../css/commentItem.css'
    // 默认情况下,如果没有为 CSS 启用模块化,则接收到的 itemStyles 是个空对象,因为 .css 样式表中,不能直接通过 JS 的 export defualt 导出对象
    
    // 当启用 CSS 模块化之后,导入 样式表得到的 itemStyles 就变成了一个 样式对象,其中,属性名是 在样式表中定义的类名,属性值,是自动生成的一个复杂的类名(防止类名冲突)
    import itemStyles from '../../css/commentItem.css'
    console.log(itemStyles)
    
    export default function CommentItem(props) {
      // 注意: 如果要使用 style 属性,为 JSX 语法创建的DOM元素,设置样式,不能像网页中那么写样式;而是要使用JS语法来写样式
      // 在 写 style 样式的时候,外层的 { } 表示 要写JS代码了,内层的 { } 表示 用一个JS对象表示样式
      // 在 style 的样式规则中,如果 属性值的单位是 px, 则 px 可以省略,直接写一个 数值 即可
    
      //#region 样式优化1
      /*  const boxStyle = { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 }
       const titleStyle = { fontSize: 16, color: "purple" }
       const bodyStyle = { fontSize: 14, color: "red" } */
      //#endregion
    
      //#region 样式优化2 把 样式对象,封装到唯一的一个对象中
      /* const inlineStyles = {
        boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 },
        titleStyle: { fontSize: 16, color: "purple" },
        bodyStyle: { fontSize: 14, color: "red" }
      } */
      //#endregion
    
      /* return <div style={inlineStyles.boxStyle}>
        <h1 style={inlineStyles.titleStyle}>评论人:{props.user}</h1>
        <h3 style={inlineStyles.bodyStyle}>评论内容:{props.content}</h3>
      </div> */
    
      return <div className={itemStyles.box}>
        <h1 className={itemStyles.title}>评论人:{props.user}</h1>
        <h3 className={itemStyles.body}>评论内容:{props.content}</h3>
      </div>
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值