React入门

React——用于动态构建用户界面的 JavaScript 库(声明式编码、组件化模式)

React Native开发移动端原生应用

JSX—— JavaScript XML

 

1、基本使用:

  1.1:相关库

  1. react.js:React核心库。
  2. react-dom.js:提供操作DOM的react扩展库。
  3. babel.min.js:解析JSX语法代码转为JS代码的库。

  1.2:JS创建基本语法:React.createElement(component, props, ...children)

  1.3:JSX创建基本语法:直接写html结构即可,babel转义成js。其就是 js 创建语法的语法糖

  1.4:渲染基本语法:ReactDOM.render(virtualDOMcontainerDOM)

  1.5:语法规则:  1、定义虚拟DOM无引号;

          2、标签混入js表达式(不是js语句),用{};

            这里要区分一下什么是表达式什么是语句(代码):

              1、语句:if、for等,不产生值的代码,控制代码逻辑的

               2、表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,比如a、a+b、demo(1),其实就是等号右边

          3、标签类名用className,不用class,因为ES6有关键字class

          4、内联样式,要用{{ }},最外层相当于jsx的执行,如2,里层为对象,所以为双花括号

          5、JSX虚拟DOM只能有一个根标签

          6、JSX标签必须闭合,<input type="text"/>或者<input type="text"></input>

          7、标签首字母:

            (1)若小写开头,则将该标签转为html中的同名元素;若无该标签,则报错

            (2)若大写,则认为是组件

  1.6:模块、组件、模块化与组件化

    模块(JS模块):向外提供特定功能的js程序, 一般就是一个js文件,复用js, 简化js的编写, 提高js运行效率。

    组件(大于模块,因为其包含JS,还有html、css、video等等):用来实现局部功能效果的代码和资源的集合(html/css/js/image等等),复用编码, 简化项目编码, 提高运行效率

    模块化的:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

    组件化的:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

 

2、面向组件编程(函数式、类式)

  函数式组件:

  <script type="text/babel">
    //函数式组件
    function Demo(){
      console.log(this);  //此处this为undefined,因为babel属于严格模式
      return <h2>函数式组件</h2>
    }
    //渲染组件到页面,组件首字母大写,标签闭合
    ReactDOM.render(<Demo/>,document.getElementById('test'))
    /*
    1、react解析组件标签,发现是函数定义
    2、随后执行函数返回VDom
    3、再解析为真实Dom,插入页面
    */
  </script>

  类式组件:

  <script type="text/babel">
    //类式组件
    class MyComponent extends React.Component{
      //这里的render是放在了原型上,供实例使用
      //render中的this是MyComponent组件实例对象,包括React和MyComponent的内容
       render(){
         return <h2>类式组件</h2>
       }
    }
    //渲染组件
    ReactDOM.render(<MyComponent/>,document.getElementById('test'))
    /*
    1、react解析组件标签,发现是类定义
    2、随后react内部new创建实例,调用原型render得到VDom
    3、再解析为真实Dom,插入页面
    */
  </script>

 

3、组件实例的三大核心属性这里我们研究class,因为最初是只有class才有状态,才有this,才是复杂组件,才有这三大核心属性。新版react给函数式定义了hooks,暂且按下不表):

  1、state(因为学过vue,且都是数据驱动,这里完全可以看做是vue中的data,其也不能直接修改,需要用setState):

    值是对象(可以包含多个key-value的组合),通过更新组件的state来更新对应的页面显示(重新渲染组件)

    state的使用要注意一个点就是this的指向问题:函数的this,使用bind或者箭头函数解决

    学习版的react,根据顺序来捋清逻辑:

  <script type="text/babel">
    //1、创建class组件
    class Weather extends React.Component{
      constructor(props){
        //1.重写构造器
        super(props)
        //2.给实例对象上的state赋值,初始化状态。state其实在React上
        this.state = {isHot:true,wind:'大风'}
        //7.为了改变changeHot的this指向,将原型上的changeHot放到构造函数本身,这是一种解决方法
        //this.changeHot = this.changeHot.bind(this)
      }
      render(){
        //3.解构,读取状态
        const {isHot,wind} = this.state
        //4.尽量以原生事件来写,{}绑定事件,不要立即执行
        return <h1 onClick={this.changeHot}> 今天{isHot?'炎热':'凉爽'},而且很{wind}</h1>
      }
      //5.方法都写到组件内,清晰
      //8.还可以使用赋值+箭头函数,箭头函数的this为上下文
      changeHot=()=>{
        //6.这里的this是undefined,render的this指向实例是因为2渲染的时候调用了他
        //6.这里的changeHot没有this,因为一开始的时候onClick并没有执行,只是赋值,所以其应该指向window,但是class默认开启严格模式,所以为undefined
        //9.状态数据state,不能直接修改或更新
        //this.state.isHot = !this.state.isHot
        //10.使用setState,合并修改对象
        let isHot = this.state.isHot
        this.setState({isHot:!isHot})
      }
    }
    //2、渲染
    ReactDOM.render(<Weather/>,document.getElementById('test'))
  </script>

    稍微简化:

  <script type="text/babel">
    class Weather extends React.Component{
      //初始化原型的state
      state = {isHot:true,wind:'大风'}

      render(){
        const {isHot,wind} = this.state
        return <h1 onClick={this.changeHot}> 今天{isHot?'炎热':'凉爽'},而且很{wind}</h1>
      }

      //自定义函数,赋值语句+箭头函数
      changeHot=()=>{
        let isHot = this.state.isHot
        this.setState({isHot:!isHot})
      }
    }

    ReactDOM.render(<Weather/>,document.getElementById('test'))
  </script>

   2、props(也和Vue的props传输类似,外部传进组件内部的值)

    每个组件对象都会有props(properties的简写)属性,组件标签的所有属性都保存在props中

    组件内部不要修改props数据

  <script type="text/babel">
    class Person extends React.Component{
      render(){
        const {name,gender,age} = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{gender}</li>
            <li>年龄:{age}</li>
          </ul>
        )
      }
    }
    ReactDOM.render(<Person name='tom' gender='男' age='18'/>,document.getElementById("test1"))
    ReactDOM.render(<Person name='jacky' gender='男' age='18'/>,document.getElementById("test2"))
  </script>

    1、展开运算符...:一次传递多个属性,但是要注意传进去的属性名要和组件内的对齐。

   这里的展开运算符需要注意:展开运算符可以展开数组,但是不能直接展开一个对象,ES6中使用{...p}是表示深拷贝一个对象。但是这里的花括号没有意义,只是单纯的做一个分隔符,花括号内部是在执行JS,react和babel解析了...,所以可以使用展开运算符展开对象

    ReactDOM.render(<Person name='tom' gender='男' age='18'/>,document.getElementById("test1"))
    const p = {"name":'tom' ,"gender":'男' ,"age":'18'}
    ReactDOM.render(<Person {...p}/>,document.getElementById("test2"))

    2、props类型限制:propTypes。其实有点像是TS了,对属性进行各种限制

    3、props默认值:defaultProps。也可以把下面的属性直接放进Person类里面

    Person.propTypes={  //P小写是因为react的限制,内部需要遍历到这个属性,才可以有限制
      name: PropTypes.string.isRequired,  //name必须有,是字符。这里的P大写是因为调用了库
      gender: PropTypes.string,  //gender可以没有,是字符
      speak: PropTypes.func  //speak限制为方法,为了不和关键字冲突,库默认方法属性叫func
    }
    Person.defaultProps={
      gender: '男',
      age: 16
    }
//直接写类里,创建属于类的属性或方法需要使用静态,可以不通过实例进行访问。这里要重点关注实例属性和类属性的区别
class Person extends React.Component{
     static propTypes={
      name: PropTypes.string.isRequired,  //name必须有,是字符
      gender: PropTypes.string,  //gender可以没有,是字符
      speak: PropTypes.func  //speak限制为方法,为了不和关键字冲突,库默认方法属性叫func
    }
    static defaultProps={
      gender: '男',
      age: 16
    }
}

    4、props是只读的,不可修改(和vue也一样了,防污染,不应该修改外面传进来的值)

    5、构造器:可以不写,因为要写的时候所考虑的情况我们都处理掉了。

        官网描述:通常,在 React 中,构造函数仅用于以下两种情况:

          1、通过给this.state赋值对象来初始化内部state。我们外部赋值了state

          2、为事件处理函数绑定实例。我们通过箭头函数解决了

      constructor(props){
        super(props)
        this.state = {isHot:true,wind:'大风'}  //已经外部赋值
        this.changeHot = this.changeHot.bind(this)  //箭头函数已解决this问题
      }

    6、函数式组件的props:一般来说函数因为在严格模式下没有所谓的this,所以就不会有state和refs,但因为可以传参,所以可以使用props(后面降到hooks可以用),其限制只能像上述的写在函数外,而不能写在函数内部

    function Person(props){
      return (
          <ul>
            <li>姓名:{props.name}</li>
            <li>性别:{props.gender}</li>
            <li>年龄:{+props.age+1}</li>
          </ul>
        )
    }
    ReactDOM.render(<Person name='tom' gender='男' age='18'/>,document.getElementById("test1"))

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

    1、基本形式:但是这个不推荐

  • 当 ref 定义为string 时,需要React实时追踪当前正在渲染的组件,在 reconciliation 阶段, React Element 创建和更新的过程中, ref 会被包装为一个闭包函数, 等待 commit 阶段被执行,这会对React 的性能产生一些影响。
  • 当使用 render callback 模式的时候,使用 string ref 会造成 ref 挂载位置产生歧义。
  • string ref 无法被组合,例如一个第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref 了,而 callback ref 可完美解决此问题。
    class Person extends React.Component{
      showData=()=>{
        // const input1 = document.getElementById('right')
        alert(this.refs.left.value)
      }
      render(){
        return (
          <div>
            <input type="text" ref='left'/>
            <button onClick={this.showData}>点击弹框</button>
          </div>
        )
      }
    }

    ReactDOM.render(<Person/>,document.getElementById("test1"))

    2、回调函数形式:如果使用内联函数,组件更新的时候其回调函数会执行两次。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题。但是执行两次也并不影响

    class Person extends React.Component{
      state = {isHot:true}
      // 类绑定,更新时只会执行一次
      changeState=(c)=>{
        const {isHot} = this.state
        this.setState({isHot:!isHot})
        console.log(c);

      }
      showData=()=>{
        alert(this.left.value)
      }
      render(){
        const {isHot} = this.state
        return (
          <div>
            {/*内联形式的回调在更新时会执行两次*/}
            {/*<p onClick={this.changeState} ref={(c)=>c}>今天很{isHot?'热':'凉爽'}</p>*/}
            <p onClick={this.changeState} ref={this.changeState}>今天很{isHot?'热':'凉爽'}</p>
            <input type="text" ref={c=>this.left = c}/>
            <button onClick={this.showData}>点击弹框</button>
          </div>
        )
      }
    }

    3、creatRef:React.creatRef()调用后会返回一个储存被ref标识的节点的容器。但该函数只能包含一个节点,如果有多个节点需要使用ref,则要创建多个

    class Person extends React.Component{
      myRef = React.createRef()
      myRef2 = React.createRef()
      showData=()=>{
        alert(this.myRef.current.value)
      }
      render(){
        return (
          <div>
            <input type="text" ref={this.myRef}/>
            <button ref={this.myRef2} onClick={this.showData}>点击弹框</button>
          </div>
        )
      }
    }

    4、ref中的事件处理

      1. 通过onXxx属性指定事件处理函数(注意大小写)

        1) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——兼容性

        2) React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——性能

      2. 通过event.target得到发生事件的DOM元素对象——不要过渡使用ref,发生事件的节点正好是要操作的节点的时候可以用event.target拿到

    class Person extends React.Component{
      showData=(event)=>{
        alert(event.target.value)
      }
      render(){
        return (
          <div>
            <input type="text" onBlur={this.showData}/>
          </div>
        )
      }
    }

 

4、表单数据:表单提交会默认触发页面跳转。Vue的双向绑定,React没有实现,所以可以手动用onChange实现

  非受控组件:现用现取,等值输入完了再去拿。只有输入完提交了才能校验

    class Login extends React.Component{
      collect = (e)=>{
        e.preventDefault()
        const {user, psw} = this
        alert(`账号为:${user.value};密码为:${psw.value}`)
      }
      render(){
        return (
          <form action="" onSubmit={this.collect}>
           用户名:<input ref={c=>this.user=c} type="text"/> <br/>
           密码:<input ref={c=>this.psw=c} type="password"/>  <br/>
           <button>登录</button>
          </form>
        )
      }
    }

  受控组件:随用随取,state随时维护输入的值,可随时进行数据校验。少用ref

    class Login extends React.Component{
      state ={name:'',psw:''}
      dataName=(e)=>{
        this.setState({name:e.target.value})
      }
      dataPsw=(e)=>{
        this.setState({psw:e.target.value})
      }
      collect = (e)=>{
        e.preventDefault()
        const {name, psw} = this.state
        alert(`账号为:${name};密码为:${psw}`)
      }
      render(){
        return (
          <form action="" onSubmit={this.collect}>
           用户名:<input onChange={this.dataName} type="text"/> <br/>
           密码:<input onChange={this.dataPsw} type="password"/>  <br/>
           <button>登录</button>
          </form>
        )
      }
    }

 

5、高阶函数:如果参数或者返回值是函数,那就是高阶函数,比如闭包、Promise、setTimeout、数组的操作等

    class Login extends React.Component{
      state ={name:'',psw:''}
      data=(dataType)=>{
        //因为回调函数加了括号会立即执行,会没有返回值。这里使用闭包,将函数作为返回值
     //e是onChange给的,所以要在返回的函数里传进来 return (e)=>{ //这里的dataType在赋值对象的时候,不能直接引用,对象会默认解析为字符串dataType //逻辑就是a=name,对象的Obj.a和Obj[a]的区别 this.setState({[dataType]:e.target.value}) } } collect = (e)=>{ e.preventDefault() const {name, psw} = this.state alert(`账号为:${name};密码为:${psw}`) } render(){ return ( <form action="" onSubmit={this.collect}> 用户名:<input onChange={this.data('name')} type="text"/> <br/> 密码:<input onChange={this.data('psw')} type="password"/> <br/> <button>登录</button> </form> ) } }

  函数柯里化多态。根据参数,处理不同参数

  纯函数:1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

      2. 必须遵守以下一些约束  

        1) 不得改写参数数据

        2) 不会产生任何副作用,不能做例如网络请求(失败),输入和输出设备(丢失)

        3) 不能调用Date.now()或者Math.random()等不纯,会改变的方法  

      3. redux的reducer函数必须是一个纯函数

 

6、组件的生命周期

  1、 组件从创建到死亡它会经历一些特定的阶段。React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

  2、旧生命周期

  

  • 初始化阶段: 由ReactDOM.render()触发---初次渲染:1. constructor();2. componentWillMount();3. render();4. componentDidMount()
  • 更新阶段:由组件内部this.setSate()或父组件重新render触发:1. shouldComponentUpdate();2. componentWillUpdate();3. render();4. componentDidUpdate();

  • 卸载阶段:由ReactDOM.unmountComponentAtNode()触发:componentWillUnmount()

   3、新生命周期

 

  • 初始化阶段: 由ReactDOM.render()触发---初次渲染:1. constructor();2. getDerivedStateFromProps();3. render();4. componentDidMount()
  • 更新阶段:由组件内部this.setSate()或父组件重新render触发:1. getDerivedStateFromProps(); 2. shouldComponentUpdate();3. render();4. getSnapshotBeforeUpdate(); 5. componentDidUpdate();

  • 卸载阶段:由ReactDOM.unmountComponentAtNode()触发:componentWillUnmount()

  新React考虑使用异步渲染,为了防止滥用误用出现未知bug,所以有三个旧钩子可能被删除:componentWillMount();componentWillUpdate();componentWillReceiveProps();

  添加了两个新钩子:getDerivedStateFromProps(); getSnapshotBeforeUpdate(); 

    1、getDerivedStateFromProps(props,state); 静态方法,参数接收props/state,控制state的渲染,state的状态完全取决于所返回的是props还是state。如果需要state的值一直等于props的值,就去用。一般不会去控制。

      static getDerivedStateFromProps(props,state){
        console.log('getDerivedStateFromProps');
        // return null
        // return props
        return state
      }

    2、getSnapshotBeforeUpdate();在componentDidUpdate的时候会传入三个参数,preProps、preState和snapshot。一般用来捕获在渲染前的一些信息,因为再往下就渲染完成,获取不到之前的一些数值了。比如滚动,一般也不用.

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 我们是否在 list 中添加新的 items ?
    // 捕获滚动​​位置以便我们稍后调整滚动位置。
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
    // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
    //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  4、主要考虑render、componentDidMount、componentWillUnmount三个钩子

 

 

7、react项目目录:

  public ---- 静态资源文件夹

  • favicon.icon ------ 网站页签图标
  • index.html -------- 主页面
  • logo192.png ------- logo图
  • logo512.png ------- logo图
  • manifest.json ----- 应用加壳的配置文件
  • robots.txt -------- 爬虫协议文件

  src ---- 源码文件夹

  • App.css -------- App组件的样式
  • App.js --------- App组件
  • App.test.js ---- 用于给App做测试
  • index.css ------ 样式
  • index.js ------- 入口文件
  • logo.svg ------- logo图
  • reportWebVitals.js--- 页面性能分析文件(需要web-vitals库的支持)
  • setupTests.js---- 组件单元测试的文件(需要jest-dom库的支持)

 

8、样式要注意,因为其不像Vue有Scope,所以要注意样式的模块化:1、最简单的其实还是class不同名;2、使用模块化引入样式index.module.cs,一般不用;3、less;4、style-loader css-loader等等

 

9、组件开发流程:

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

 

10、组件信息传递:

  首先要从架构上考虑状态应该放在哪个组件最合适。操作状态的函数也将会放在该组件内(状态提升)。React是单向数据流

  父传子:props

  子传父:也可以用props,父组件给子组件传递函数,子组件执行父组件的函数,通过传参把新的props传回给父组件操作

  兄弟互传:子传父传子

  任意组件之间:Context,消息订阅与发布机制PubSubJS:一个组件发布消息与参数('topic', data),另一个组件订阅某一消息并响应执行回调获取参数('topic', responFn)

// 订阅者收到消息后的回调函数
var mySubscriber = function (msg, data) {
    console.log( msg, data );
};

// 订阅者,可以把它挂载在某一个钩子上,一般可以放在componentDidMount
var token = PubSub.subscribe('MY TOPIC', mySubscriber);

// 发布者
PubSub.publish('MY TOPIC', 'hello world!');

//取消订阅
PubSub.unsubscribe(token)
//异步发布 PubSub.publishSync('MY TOPIC', 'hello world!');

 

11、React路由

  1、单页面SPA:整个应用只有一个完整的页面。点击页面中的链接不会刷新页面,只会做页面的局部更新数据都需要通过ajax请求获取, 并在前端异步展现。

  2、路由:一个路由就是一个映射关系(key:value)。其基础就是栈结构

      具体规则:1、hash,Hash 方法是在路由中带有一个#锚点,通过监听 # 后的哈希值 URL 路径标识符的更改而触发的浏览器 hashchange 事件,然后通过获取 location.hash 得到当前的路径标识符,再进行一些路由跳转的操作,其不会页面跳转。localhost -> localhost/#/test1 ->l ocalhost/#/test2,只监听#后的变化。不美观

           2、history API,纯history操作路径通过push、replace将路径入栈出栈,go、goBack和goForward实现路径的浏览。history 提供了pushState(新增)和 replaceState(替换) 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新。但是是H5的,兼容性没hash好

    前端路由的核心就是以上两点,其他的功能类似动态路由、路由参数、路由动画,都是添加上的功能。

  3、常用组件:

    1. <BrowserRouter> history Api,一般来说整个应用是用一个路由器管理,所以该标签一般定义在index.js上。其刷新网页,维护history导致state不会消失

    2. <HashRouter> hash,同上。HashRouter刷新后,因为其底层原理并不是使用history,所以会导致路由state参数的丢失

    3. <Route> path对应路由,component对应路由组件。<Route path="/about" component={About} />(Router6中<Route>可配合useRoutes()配置路由表

    4. <Redirect> 重定向,类似自动跳转,开屏默认跳转的路由,登录成功后自动跳转之前的页面等。原理就是当其他都没匹配上,就再去找Redirect。(Router6改成<Navigate to=' ' replace={false}/>)

    5. <Link> 类似<a></a>,to到指定路由。<Link to="/home">Home</Link>

    6. <NavLink> 如果点击,即追加类名activeClassName,动态激活。和Vue的RouterLink类似,依据当前路由路径和to判断是否一致来添加class。(Router6需要className的值改成函数,点击都会调用,根据参数isActive,更改类名。home子组件匹配成功,默认home高亮;若添加end属性,home不高亮

    7. <Switch>路由是按定义顺序匹配,为了提高路由匹配效率,只要匹配到,立马结束,不往下匹配。单一匹配,一般一个路由一个组件。(Router6改成<Routes/>,且必须)

  4、一般组件:引用渲染<Header/>这类,props默认为空

    路由组件:路由渲染<Route/>这类,props默认传和路由相关的东西,比如location、history和match等,类似Vue的route。这一些其实基本就是原生的BOM内容了。

      以下展示的是主要的信息,有些未展示:

      location:当前路由信息。hash/pathname/search/state

      history:管理历史路由,控制路由前进后退等。go/goBack/goForward/push/replace

      match:匹配模式,参数。isExact/params/path/url

  5、封装路由组件:其实也是当一个一般组件rcc/rfc编写,因为是用标签引入的。import路由组件的时候,根据需要,看是引入Link还是NavLink。注意标签体的传递,也是在props里,属于children,可以直接写入标签内

//封装组件
import React, { Component } from 'react'
import { NavLink, } from "react-router-dom";

export default class myNavLink extends Component {
  render() {
    return (
      <NavLink className="list-group-item" {...this.props}></NavLink>
    )
  }
}

//引用
<MyNavLink to='/home' show='Home'>Home</MyNavLink>
<MyNavLink to='/about' show='About'>About</MyNavLink>

  6、样式路径问题:如果在路由路径中使用了多级路由,在index.html引入样式时,刷新重定向后,再次请求的css文件路径可能会出现问题。可以处理一下引用路径。比如引入css时,./换成/;或者用%PUBLIC_URL%路径,这个在react用得比较多,且只能在react有效;也可以用锚点从而请求路径不带#号后的内容

  7、路径匹配:精准匹配:路由开启exact。路由和link一一对应。一般不要开启,不然会无法匹配二级路由

//路由
<Route exact={true} path="/about/a" component={About} />  //router6: element={<About/>}
//Link
<MyNavLink to="/about/a" show="About"/>

          模糊匹配:默认模式,路由去匹配Link,按照Link的顺序去匹配

//路由
<Route path="/about" component={About} />
//Link
<MyNavLink to="/about/a" show="About"/>
//错误
<MyNavLink to="/a/about" show="About"/>

  8、嵌套路由(多级路由)Vue的子路由:多级下的子组件的Link,需要包含父级的Link。先走的父级的Link,再走多级的Link。否则会匹配不到导致执行父级上的重定向回About。

同理,Link加了路径,Route也需要。这个不太像Vue一样专门一个route文件去编辑

//Route
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>

//Link
<MyNavLink to="/home/news">News</MyNavLink>
<MyNavLink to="/home/message">Message</MyNavLink>

  9、路由传参:

    1、params,动态路由。路由用/:id类型匹配,Link用模板字符串传。子组件用props.match.params接收

// 父组件
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<Route path="/home/message/detail/:id/:title" component={Detail}/>

// 子组件
const {id,title} = this.props.match.params
//所拿到的对象为{id:msgObj.id的值, title: msgObj.title的值}

    2、search,search参数通过?判断,路由并不需要匹配。获取到的search是urlencoded编码字符串,需要借助querystring解析,slice去掉?号

//父组件
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
<Route path="/home/message/detail" component={Detail}/>

//子组件
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))

    3、state,地址栏不会显示参数。因为link上要加state,而state不是显示在url上,所以这里的to是写成一个对象的形式。history维护location,即使页面刷新,url不刷新,参数也不会丢失,导致刷新其也不会跳转查到初始页面

//父组件
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
<Route path="/home/message/detail" component={Detail}/>

//子组件
const {id,title} = this.props.location.state || {}

  10、路由跳转(编程式路由导航):默认push模式,使用props.history.下的各类操作函数,push()、replace()、go()等

//比如这个例子里,各个消息都是同级的,就没必要返回的时候返回上一个消息,可以直接返回到home下,而不是3,2,1
<Link replace to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>

  11、withRouter。一般组件是没有路由组件的一些功能的,比如history,所以按道理是不能直接操控前进后退等。在一般组件内引入withRouter,将一般组件包装成路由组件,内部按照路由组件,调用this.props.history.push()等方法即可。

import {withRouter} from 'react-router-dom'
class Header extends Component{
  ...  
}
// 返回一个新组件
export default withRouter(Header)

 

12、redux。第三方出品

  redux是一个专门用于做状态管理的JS库,可以用在react、angular、vue下。现在还有很多别的,本质上都是发布订阅模式

  • 没有状态管理工具:直接用 props 或者 context
  • 单项数据流:reduxzustand
  • 双向绑定:mobxvaltio
  • 状态原子化:jotairecoil
  • 有限状态机:xstate

  何时要用到状态管理工具?1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。2. 一个组件需要改变另一个组件的状态(通信)。

  1、原理

  

 

   2、三大核心(Vue四大或者说五大,状态管理都类似)

     1、action:

        1、动作对象。同步(Vue的mutation的定义)   

        type:标识属性, 值为字符串, 唯一, 必要属性。操作

        data:数据属性, 值类型任意, 可选属性。数据

        如:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

export const createIncrementAction = data => ({type:INCREMENT,data}) //箭头函数直接返回一个对象需要用括号包着,否则会和函数体的花括号冲突

 

        2、函数。异步(Vue的action)

//这里常规的会写成store.dispatch调用分发,但是在store拦截action的时候,会查看action,其里面要用一般对象,而不能用函数,所以
          action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
//action内
export const createIncrementAsyncAction = (data,time) => {
    //这里store调用的dispatch,所以也会默认的传一个dispatch过来。当然,在action再引用store,再store.dispacth也可以
return (dispatch)=>{ setTimeout(()=>{        //这里通常会理解写成store.dispatch...,但是在store拦截action的时候其检查内部不是对象(这里我们写成了函数)会报错        //所以我们使用中间件去封装dispatch:redux-thunk,有了这个才能异步 dispatch(createIncrementAction(data)) },time) } }

//store内引入redux-thunk
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))
 

    2、reducer(Vue的mutation:初始化状态(undefined,初始化),加工状态(旧state,动作)。其是根据旧的state和action, 产生新的state的纯函数,preState不能改变。

const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
    //从action对象中获取:type、data
    const {type,data} = action
    //根据type决定如何加工数据
    switch (type) {
        case INCREMENT: //如果是加
     //这里要注意,如果preState是一个对象,比如数组,若直接返回preState则不生效,因为redux会做对比,不同才return新值。
     //所以可一个解构返回新数组
return preState + data case DECREMENT: //若果是减 return preState - data default: return preState } }

 

    3、store(Vue的state):将state、action、reducer联系在一起的对象 

        创建store:

        1) import {createStore} from 'redux'

        2) import reducer from './reducers'   --> 纯操作函数

        3) const store = createStore(reducer)

        使用store的步骤:

        1) store.getState(): 得到state

        2) store.dispatch(action): 分发action, 触发reducer调用, 产生新的state

        3) store.subscribe(listener): 注册监听, 当产生了新的state时, 自动调用,执行代码,如render更新视图:我们不会去直接this.render()去调用渲染,而是会setState({}),只要调用setState,都会去执行render。改方法可以写在总的index里,只要写一次就行,整体diff

import store from './redux/store'

store.subscribe(()=>{
    ReactDOM.render(<App/>,document.getElementById('root'))
})

    tips:可以把action的一些操作名称统一通constant.js去维护,防止写错。这不单只是在redux里,算是编程的一个知识点:

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

 

13、react-redux。官方

  

 

   概念:

  

   UI:

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 通过props接收数据(一般数据和函数)

  • 不使用任何 Redux 的 API

  • 一般保存在components文件夹下

  容器(一般就是react-redux生成):

  • 负责管理数据和业务逻辑,不负责UI的呈现

  • 使用 Redux 的 API

  • 可以自动检测store变化,并自动render,不需要subscribe再render
  • 一般保存在containers文件夹下,也可以把UI和容器写一起

  1、连接:

//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//使用connect()()创建并暴露一个Count的容器组件,必须传两个函数mapStateToProps,mapDispatchToProps
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

  mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性,传状态

  mapDispatchToProps:将分发action的函数转换为UI组件的标签属性,传方法

  简写,把mapDispatch函数简写成对象:

connect(
    state => ({key:value}), //映射状态,数据                                    
    {key:xxxxxAction} //映射操作状态的方法,操作。react-redux能dispatch自动分发,不用再写成函数的形式
)(UI组件)

 

  2、store的定义:而且因为容器包裹UI,store之和容器打交道,所以store只要在index.js里,在大封装APP,也就是app.jsx的上一层使用即可。使用Provider

import store from './redux/store'
import {Provider} from 'react-redux'

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
)

  如果要给个别的容器使用,也可以在app.jsx里使用

import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'

export default class App extends Component {
    render() {
        return (
            <div>
                {/* 给容器组件传递store */}
                <Count1 store={store} />
                <Count2 />
                <Count3 store={store} />
            </div>
        )
    }
}            

 

  3、数据共享:store共享、acitons文件共用、reducers文件公用(combineReducers合并生成对象,否则只会执行一个reducer,只能保存一种数据结构)

//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
    he:countReducer,
    rens:personReducer
})

//暴露store, 现阶段createStore已经弃用,现用legacy_createStore
export default createStore(allReducer,applyMiddleware(thunk))

  4、使用:定义好store,创建action。store内配置相应的 data:actionindex.js引入store。components内使用useSelector引入data、useDispatch调用action。

   

14、补充

   1、setState更新状态的2种写法

    (1). setState(stateChange, [callback])------对象式的setState
      1.stateChange为状态改变对象(该对象可以体现出状态的更改),就是setState({count: count+1})
      2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用。nextTick

    this.setState({ count: count+1 },()=>{
      console.log(count);
    });

    (2). setState(updater, [callback])------函数式的setState
      1.updater为返回stateChange对象的函数
      2.updater可以接收到state和props
      3.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

this.setState((state, props) => ({ count: state.count + props }));

    总结:
    1.对象式的setState是函数式的setState的简写方式(语法糖)
    2.使用原则:
      (1).如果新状态不依赖于原状态 ===> 使用对象方式
      (2).如果新状态依赖于原状态 ===> 使用函数方式,当然,对象式其实也可以。{count: this.state.count+1}

      (3).如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取

    

  2、路由组件lazyload:

    //1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
    const Login = lazy(()=>import('@/pages/Login'))
    
    //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
    <Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>

 

  3、Hooks:可以在函数组件中使用 state 以及其他的 React 特性。函数式组件没有this

  (1). State Hook: React.useState()

  (2). Effect Hook: React.useEffect()

  (3). Ref Hook: React.useRef()

    1、State Hook

      (1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作

      (2). 语法: const [xxx, setXxx] = React.useState(initValue)
      (3). useState()说明:
        参数: 第一次初始化指定的值在内部作缓存
        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
      (4). setXxx()2种写法:
        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

export default function Index(props) {
  const [count, setCount] = React.useState(0);
  const [person] = React.useState({ name: "tom", age: 18 });
  function add() {
    // setCount(count=>count+1)
    setCount((count) => count + 1);
  }
  return (
    <div>
      <h2>当前求和:{count}</h2>
      <h2>当前姓名:{person.name}</h2>
      <h2>当前年龄:{person.age}</h2>
      <button onClick={add}>+1</button>
    </div>
  );
}

    2、Effect Hook

      (1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
      (2). React中的副作用、使用生命周期操作:
        发ajax请求数据获取
        设置订阅 / 启动定时器
        手动更改真实DOM
      (3). 语法和说明:
        useEffect(() => {
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
          // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行。如果不传,则监测所有state;如果传空数组,则谁也不监测

  //effect hook 相当于三个生命钩子
  React.useEffect(() => {
    //DidMount
    let timer = setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
    //WillUnmount
    return ()=>{
      clearInterval(timer)
    }
    //只检测count,DidUpdate
  }, [count]);

      (4). 可以把 useEffect Hook 看做如下三个函数的组合
        componentDidMount()
        componentDidUpdate()
        componentWillUnmount()

    3、Ref Hook 

      (1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
      (2). 语法: const refContainer = useRef()
      (3). 作用:保存标签对象,功能与React.createRef()一样

    4、新版Reactrouter新增多个hook

      useRoutes()配置路由表。类似Vue的路由表,嵌套路由也是和vue一个套路;配合<Outlet/>指定路由组件在别的组件内的位置

      useParams()获取路由参数。由于函数式组件没有this,拿不到history里的参数,所以要使用hook,返回qs分割好的对象

      useMatch()获取路由match参数,需要传参路径

      useSearchParams()获取路由Search参数,返回数组,第一个是参数,第二个是setSearch  

      useLocation()获取location参数

      useNavigate()使用history,因为函数式没有this,拿不到history,有时候需要在函数内跳转页面,需要用到history,可以replace,传参等  

      useInRouterContext()判断是否在路由上下文环境内,返回判断

      useNavigationType()返回当前页面导航类型:pop刷新页面、push、replace

      useOutlet()用来呈现当前组件中渲染的嵌套路由,也就是当前组件内的子路由,该子路由需要挂载才能返回

      useResolvedPath()解析路径,传入路径,类似qs          

 

  4、<Fragment><Fragment>。相当于Vue的template

  5、Context。一种组件间通信方式, 常用于【祖孙组件】与【后代组件】间通信。一般应用开发不用,开发插件用。有些类似Vue的 provide 和 inject

    1) 创建Context容器对象:写在所有组建的外侧,类似redux共享 
      const XxxContext = React.createContext()   首字母大写
    2) 父组件渲染子组时,在子外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:

        <xxxContext.Provider value={数据}>
            子组件
        </xxxContext.Provider>

    3) 后代组件读取数据:

      //第一种方式:仅适用于类组件
      static contextType = xxxContext // 先声明接收context
        this.context // 然后,读取context中的value数据
      //第二种方式: 函数组件与类组件都可以
      <xxxContext.Consumer>
        {value => ( // 函数接收参数value,就是context中的value数据要显示的内容)}
      </xxxContext.Consumer>

  6、PureComponent

    其实主要是优化shouldComponentUpdate。当父组件更新时,控制子组件的更新,比如子组件没用到父组件的数据时,子组件不应该更新。    

      办法1:重写shouldComponentUpdate()方法比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

     shouldComponentUpdate(nextProps,nextState){
        // console.log(this.props,this.state); //目前的props和state
        // console.log(nextProps,nextState); //接下要变化的目标props,目标state
        return !this.props.carName === nextProps.carName //一般来说子组件的判断用props,因为是获取的props;父组件判断是state
    } 

      办法2:使用PureComponent,PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true

class Child extends PureComponent{}

    注意:
      只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false。可以说不能监听Object改变。为了避免,就应该去返回新对象,彻底改变,而不是在原有的基础上修改,返回修改后的老对象。
      不要直接修改state数据, 而是要产生新数据
      项目中一般使用PureComponent来优化,shouldComponentUpdate需要手动指定

  7、render props(Vue的插槽)向组件内动态传入带内容的标签。就是第三方组件插入插槽

//传统嵌套,children props
<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 
//render props,通过render函数传递
//slot
class A extends Component {
    state = {name:'tom'}
    render() {
        console.log(this.props);
        const {name} = this.state
        return (
            <div className="a">
                <h3>我是A组件</h3>
                {this.props.render(name)}  //slot插槽预留的位置
            </div>
        )
    }
}

//引用
<A render={(name)=><B name={name}/>}/>

//要插入的B
class B extends Component {
    render() {return (
            <div className="b">
                <h3>我是B组件,{this.props.name}</h3>
            </div>
        )
    }
}

  8、错误边界:用来捕获后代组件错误,渲染出备用页面,防止整个页面崩溃。类似自定义局域的404页面

    只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

    使用:getDerivedStateFromError配合componentDidCatch。不过一般用在生产环境 ,因为显示的时间较短。

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    //render之前触发
    // 返回一个新的state状态,将之前定义的state修改。state修改就可以渲染自定义的404或别的错误页面
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台
    console.log(error, info);
}

  9、组件间通信

  组件间的关系:1、父子组件;2、兄弟组件(非嵌套组件)3、祖孙组件(跨级组件)

  几种通信方式:

    1.props:
      (1).children props
      (2).render props
    2.消息订阅-发布:
      pubs-sub、event等等
    3.集中式管理:
      redux、dva等等
    4.conText:
      生产者-消费者模式,provider

  常见搭配:

    父子组件:props
    兄弟组件:消息订阅-发布、集中式管理
    祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

 

 

官网和其他文档的一些补充:

1、React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。

 

 2、Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。JSX是其语法糖

// 注意:这是简化过的结构
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

  这个称为react元素。虚拟dom

 

3、State 的更新可能是异步的

  因为 this.props 和 this.state 可能会异步更新,所以不要依赖他们的值来更新下一个状态。要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

 

4、Ref 转发是一项将ref自动地通过组件传递到其一子组件的技巧。一般也不会用

//子组件
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // 你可以直接获取 DOM button 的 ref:指定ref为jsx的props const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;

  第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。

 

5、Fragments。React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。其实类似vue的template生成代码片段,渲染的时候标签会自动消失

//父组件
class Table extends React.Component {
  render() {
    return (
      <table>
        <tr>
          <Columns />
        </tr>
      </table>
    );
  }
}

//子组件
class Columns extends React.Component {
  render() {
    return (
      <React.Fragment key等props属性可以写>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}

//唯一根,包裹div会不生效
class Columns extends React.Component {
  render() {
    return (
      <div>
        <td>Hello</td>
        <td>World</td>
      </div>
    );
  }
}

 

6、高阶组件。高阶组件是参数为组件,返回值为新组件的函数。比如withRouter封装的组件

const NavbarWithRouter = withRouter(Navbar);
// React Redux 的 `connect` 函数
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

  不要在render中使用HOC,不单只是性能问题,还有可能造成数据丢失

render() {
  // 每次调用 render 函数都会创建一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
  return <EnhancedComponent />;
}

 

7、可以使用点语法来引用一个 React 组件。当你在一个模块中导出许多 React 组件时,例如,如果 MyComponents.DatePicker 是一个组件,可以在 JSX 中直接使用:

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;
}

 

8、由于react的diff并不是很精细,父组件一旦更新,其下子组件也将更新。为了减少性能损失的影响,可以使用shouldComponentUpdate去控制子组件的更新。现阶段一般使用PureComponent

 

9、Protals。

ReactDOM.createPortal(child, container)

一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。使用时要注意事件冒泡等的设置

 

10、Hook使用规则:

  Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。react是根据hook的调用顺序来判断哪个state对应哪个setState的,如果放在函数内,比如if,很有可能导致hook丢失,从而导致对应出错。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中)

 

11、自定义Hook:其实可以理解为公用的方法,接收参数、返回值,内部可以调用别的hook来完成逻辑

// 自定义
import React, { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
// 调用
const isOnline = useFriendStatus(props.friend.id);

  组件被调用其完全独立,复用状态逻辑,即使是同时调用同一个自定义hook的组件,其state也不会相同。自定义hook必须以use开头

 

12、React采用Object.is(value1, value2)来判断是否为同一个值

  Object.is() 方法判断两个值是否为同一个值,如果满足以下任意条件则两个值相等:

  • 都是 undefined
  • 都是 null
  • 都是 true 或都是 false
  • 都是相同长度、相同字符、按相同顺序排列的字符串
  • 都是相同对象(意味着都是同一个对象的值引用)
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都是同一个值,非零且都不是 NaN

  Object.is() 与 == 不同。== 运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换(这种行为将 "" == false 判断为 true),而 Object.is 不会强制转换两边的值。

  Object.is() 与 === 也不相同。差别是它们对待有符号的零和 NaN 不同,例如,=== 运算符(也包括 == 运算符)将数字 -0 和 +0 视为相等,而将 Number.NaN 与 NaN 视为不相等。

 

 

 

posted @ 2022-05-27 21:56  Jacky02  阅读(137)  评论(0编辑  收藏  举报