原生JavaScript的痛点

  1. 原生JavaScript操作DOM 繁琐、效率低(DOM-API 操作 UI

  2. 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排

  3. 原生JavaScript没有组件化编码方案,代码复用率低

React的特点

  1. 采用组件化模式、声明式编码,提高开发效率及组件复用率。
  2. 在React Native种可以使用React语法进行移动端开发
  3. 使用虚拟DOM+优秀的Diffing 算法,尽量减少与真实DOM的交互

关于虚拟DOM

  1. 本质是Object类型的对象(一般对象)
  2. 虚拟DOM比较“轻” 真实DOM比较“重”,因为虚拟DOM是React 内部再用,无需真实DOM上那么多的属性
  3. 虚拟DOM最终会被React转化为真实DOM 呈现再页面上

babel=>

  1. es5=>es6
  2. jsx=>js

JSX

  • 全称:JavaScript XML
  • react 定义的一种类似于XML的JS 扩展语法:JS+XML
  • 本质是React.createElement(component,props,...children)方法的语法糖
  • 作用:用来简化创建虚拟DOM
  1. 写法:var ele = <h 1>Hello JSX< /h1 >
  2. 注意1:它不是字符串,也不是HTML/XML标签
  3. 注意2:它最终产生的就是一个JS对象
  • 标签名任意:HTML标签或其他标签

JSX语法规则:

  1. 定义虚拟DOM时,不要写引号

  2. 标签中混入JS表达式时要用{}

    注意区分【js语句(代码)】与【js表达式】

    1.表达式:一个表达式会产生一个值,可以放在任何一个需要值得地方
        下面这些都是表达式:
    		(1).a
            (2).a+b
            (3).demo(1)
            (4).att.map()
            (5).function test() {}
     2.语句(代码)
         下面这些都是语句(代码)
             (1).if(){}
             (2).for(){}
             (3).switch(){case:xxxx}
    
  3. 样式的类名指定不要用class,要用className

  4. 内联样式 要用style={{kry:value}}的形式去写

  5. 虚拟DOM必须只有一个根标签

  6. 标签必须闭合

  7. 标签首字母

  • 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
  • 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

模块与组件、模块化与组件化的理解

  • 模块(主要是拆分js)
  1. 理解:向外提供特定功能的js程序,一般iu是一个js文件
  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂
  3. 作用:复用js,简化js的编写,提高js运行效率
  • 组件
  1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/音视频等)
  2. 为什么:一个界面的功能更复杂
  3. 作用:复用编码,简化项目编码,提高运行效率
  • 模块化 应用的js都是以模块来编写
  • 组件化 应用是以多组件的方式实现

函数式组件

执行了ReactDOM.render(< MyComponent/ >,document.getElementById('test'))之后,发生了什么

  1. React解析组件标签,找到了MyComponent组件
  2. 发现组件时使用函数定义的,随后调用该函数,将返回的虚拟DOM转化为真实DOM,随后呈现在页面中

类式组件

类的基本

  1. 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时采写
  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的
  3. 类中所定义的方法,都放在了类的原型对象上,供实例去调用。

执行了ReactDOM.render(< MyComponent/ >,document.getElementById('test'))之后,发生了什么

  1. React解析组件标签,找到了MyComponent组件
  2. 发现组件时使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
  3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中

组件实例的三大核心属性1:state

  • 理解
  1. state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
  2. 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
  • 强烈注意
  1. 组件中render方法中的this为组件实例对象

  2. 组件自定义的方法中this为undefined,如何解决?

    a. 强制绑定this:通过函数对象的bind()

    b. 箭头函数

  3. 状态数据,不能直接修改或更新

组件实例的三大核心属性2:props

  • 理解
  1. 每个组件对象都会有props(properties的简写)属性
  2. 组件标签的所以属性都保存再props中
  • 作用
  1. 通过标签属性从组件外向组件内传递变化的数据
  2. 注意:组件内部不要修改props数据
  • 编码操作
  1. 内部读取某个属性值

     this.props.name
    
  2. 对props中的属性值进行类型限制和必要性限制

第一种方式(React v15.5开始已弃用)

    Person.propTypes = {
      name:React.PropTypes.string.isRequired,//限制name必传,且为字符串
      sex:React.PropTypes.string,//限制sex为字符串
      age:React.PropTypes.number,//限制age为数值
    }

第二种方式 使用prop-types库进行限制(需要引用prop-types库)

    Person.propTypes = {
      name:PropTypes.string.isRequired,//限制name必传,且为字符串
      sex:PropTypes.string,//限制sex为字符串
      age:PropTypes.number,//限制age为数值
    }

3. 扩展属性:将对象的所以属性通过props传递

    <Person {...person}/>

4. 默认属性值

    Person.defaultProps={
      sex:"不男不女",//sex默认值为不男不女
      age:18//age默认值为18
    }

5. 组件类的构造函数

    constructor(props){
      super(props)
      console.log(props)
    }

组件实例的三大核心属性3:ref

组件内的标签可以定义ref属性来标识自己

  1. 字符串形式的ref

     <input ref='input1' .../>
    
  2. 回调形式的ref

     <input ref={(c)={this.input1= c}}
    
  3. createRef创建ref容器

     myRef = React.createRef()
     <input ref = {this.myRef}/>
    

事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)
  • React使用的是自定义(合成)事件,而不是使用的原生DOM事件
  • React 种的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  1. 通过event.target 得到发生事件的DOM 元素对象

收集表单数据 包含表单的组件分类

  • 受控组件
  • 非受控组件

组件的生命周期

  1. 组件从创建到死亡它会经历一些特定的阶段
  2. React组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用
  3. 我们在定义组件时,会在特定的生命周期回调函数中,作特定的工作
  • 生命周期原理图(旧) alt
  1. 初始化阶段: 由ReactDOM.render()触发---初次渲染

    1. constructor()
    2. componentWillMount()
    3. render()
    4. componentDidMount()
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

    1. shouldComponentUpdate()
    2. componentWillUpdate()
    3. render()
    4. componentDidUpdate()
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount()
  • 生命周期原理图(新) alt
  1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
    1. constructor()
    2. getDerivedStateFromProps
    3. render()
    4. componentDidMount()
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
    1. getDerivedStateFromProps
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate
    5. componentDidUpdate()
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    1. componentWillUnmount()

React 应用

功能界面的组件化编码流程(通用)

  1. 拆分组件:拆分界面,抽取组件
  2. 实现静态组件:使用组件实现静态页面效果
  3. 实现动态组件:
    1. 动态显示初始化数据
    • 数据类型
    • 数据名称
    • 保存在那个组件?
    1. 交互(从绑定事件监听开始)

todoList 案例相关知识点

  1. 拆分组件、实现静态组件,注意:className、style得写法
  2. 动态初始化列表,如何确定将数据放在哪个组件得state中?
    1. 某个组件使用:放在自身得state中
    2. 某些组件使用:放在他们共同得父组件state中(官方称此操作为:状态提升)
  3. 关于父子之间通信
    • 【父组件】给【子组件】传递数据:通过props传递
    • 【子组件】给【父组件】传递数据:通过props传递,要求父组件提前给子组件传递一个函数
  4. 注意defaultChecked 和checked 的区别,类似的还有LdefaultValue 和 value
  5. 状态在哪里,操作状态的方法就在哪里

github搜索案例相关知识点

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办

  2. ES6小知识点: 解构赋值+重命名

     let obj = {a:{b:1}}
     const {a} = obj//传统解构赋值
     const {a:{b}} = obj //连续解构赋值
     const {a:{b:value}} = obj //连续解构赋值+重命名
    
  3. 消息订阅与发布机制

    1. 先订阅,再发布(理解:有一种隔空对话的感觉)
    2. 适用于任意组件间通信
    3. 要在组建的compontWithUnmount中取消订阅
  4. fetch发送请求(关注分离的设计思想)

     try{
       const response = await fetch(`/api1/search/users?q=${keyWord}`)
       const data = await respnse.json()
       console.log(data)
     }catch(error){
       console.log('请求出错',error)
     }
    

React 路由

  • SPA的理解
  1. 单页Web应用(single page web applicat,SPA)
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接 不会刷新页面,只会做页面的局部更新
  4. 数据都需要通过ajax请求获取,并在前端异步展现
  • 路由的理解
  1. 一个路由就是一个映射关系(key:value)
  2. key 为路径,value可能是function 或 component
  • 路由分类
    1. 后端路由
    2. 理解:value 是 function,用来处理客户端提交的请求
    3. 注册路由:router.get(path,function(req,res))
    4. 工作过程: 当node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
    5. 前端路由
    6. 浏览器端路由,value时component,用于展示页面内容
    7. 注册路由:
    8. 工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件

路由的基本使用

  1. 明确好界面中的导航区、展示区

  2. 导航区的a标签改为Link标签

    < Link to="/xxxx">demo</ Link>

  3. 展示区写Router标签进行路径的匹配

    < Route path="/xxxx" component={Demo}/>

  4. 的最外侧包裹了一个< BrowerRouter>或< HashRouter>

路由组件与一般组件

  1. 写法不同:

    一般组件:< Demo />

    路由组件:< Route path="/demo" component={Demo}/>

  2. 存放位置不同:

    一般组件:components

    路由组件:pages

  3. 接收到的props不同:

    一般组件:写组件标签时传递了什么,就能收到什么

    路由组件:接收到三个固定的属性

    history: go: ƒ go(n) goBack: ƒ goBack() goForward: ƒ goForward() push: ƒ push(path, state) replace: ƒ replace(path, state) location: pathname: "/about" search: "" state: undefined match: params: {} path: "/about" url: "/about"

NavLink 和 封装 NavLink

  1. NavLink 可以实现路由链接的高亮,通过activeClassName 指定样式名
  2. 标签体内容时一个特殊的标签属性
  3. 通过 this.porps.children 可以获取标签体内容

Switch 的使用

  1. 通常情况下,path 和component是一一对应的关系
  2. Switch 可以提高路由匹配效率(单一匹配)

解决多级路径刷新页面样式丢失的问题

  1. public/index.html 中 引入样式时不写 ./ 写 /(常用)
  2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL%(常用)
  3. 使用 HashRouter

路由的严格匹配与模糊匹配

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
  2. 开启严格匹配:< Route exact={true} path="/about" component={About}/>
  3. 严格匹配不要随便开启,需要再开,有些时候开启回导致无法继续匹配二级路由

Redirect的使用

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
  2. 具体编码:
  <Switch>
    <Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>
    <Redirect to="/about"/>
  </Switch>

嵌套路由

  1. 注册子路由时要写上父路由的path值
  2. 路由的匹配时按照注册路由的顺序进行的

向路由组件传递参数

  1. params参数

    路由链接(携带参数):

   <Link to='/demo/test/tom/18'}>详情<Link>
 注册路由(声明接收):
   <Route path='/demo/test/:name/:age'} component={Test}><Route>
  接收参数:
   const {id,title} = this.props.match.params
  1. search参数 路由链接(携带参数):
   <Link to='/demo/test?name=tom&age=18'}>详情<Link>
 注册路由(无需声明,正常注册即可):
   <Route path='/demo/test'} component={Test}><Route>
  接收参数:
   const {search} = this.props.location
   //备注:获取到的search是urlencoded编码字符串,需要借助querystring解析(新版本直接用qs)
  1. state参数
 路由链接(携带参数):
   <Link to={{pathname:'/demo/test',state:{name:'tom',age:18}>详情<Link>
 注册路由(无需声明,正常注册即可):
   <Route path='/demo/test'} component={Test}><Route>
  接收参数:
   const {id,title} = this.props.location.state
   //备注:刷新也可以保留住参数

编程式路由导航

借助this.props.history对象上的API对操作路由跳转、前进、后退

     this.props.history.push()
     this.props.history.replace()
     this.props.history.goBack()
     this.props.history.goForward()
     this.props.history.go()

BrowserRouter 和 HashRouter的区别

  1. 底层原理不一样

BrowserRouter 使用的是H5的history API,不兼容IE9及一下版本。

HashRouter 使用的是URL的哈希值

  1. path表现形式不一样

BrowserRouter 的路径中没有#,例如:localhost:3000/demo/test

HashRouter 的路径包含#,例如:localhost:3000/#/demo/test

  1. 刷新后对路由state参数的影响
  • BrowserRouter 没有任何影响,因为state保存再history对象中

  • HashRouter 刷新后对导致路由state参数的丢失

  1. 备注:HashRouter 可以用于解决一些路径错误相关的问题