React中级学习(第二天)

JSX 语法的转化过程 (了解)

演示 : babel中文网试一试 let h1 =

  • JSX 仅仅是createElement() 方法的语法糖 (简化语法)
  • JSX 语法 被 @babel/preset-react 插件编译为 createElement() 方法
  • React 元素:是一个对象,用来描述你希望在屏幕上看到的内容
  • React 元素 最后 被 ReactDOM.render(,document.getElementById('root')) 渲染显示到页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9hqEG5rw-1596329786917)(C:/Users/wangyu123/Desktop/新建文件夹/面试题/md-imgs/jsx.png)]

  • 演示:
render () {
    
    const el = <h1 className='greeting'>Hello JSX</h1>
   
    console.log(el);
    
    return el
}

react更新机制 
简化 : 虚拟DOM
然后呢?? 
1. jsx => 虚拟DOM  => 真实的DOM
2. jsx数据发生变化 => 新的虚拟DOM
3. 新的虚拟DOM 和 旧的虚拟DOM对比 => 通过Diff算法找到有差异的地方 => 更新1步骤的真实的DOM


组件更新机制

  • setState 的两个作用
    • 修改state
    • 重新调用render , 更新组件(UI)
  • 过程 : 父组件重新渲染时, 也会重新染当前组件子树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4SpZboe-1596329786920)(C:/Users/wangyu123/Desktop/新建文件夹/面试题/md-imgs/更新机制.png)]

演示代码 :

  • App > P1+P2> C1+C2
//2. 类组件
class App extends React.Component {
  state = {
  }
  render() {
    return (
      <div style={{ background:'pink', display:'flex' }}>
        <P1></P1>
        <P2></P2>
      </div>
    )
  }
}
class P1 extends React.Component {
  render() {
    return (
      <div style={{ background:'red',flex:1 }}>
        <p>P1</p>
      </div>
    )
  }
}
class P2 extends React.Component {
  state = {
    name :'zs'
  }
  render() {
    return (
      <div style={{ background: 'skyblue',flex:1 }}>
        <p onClick={this.handle}>P2- { this.state.name }</p>
        <C1></C1>
        <C2></C2>
      </div>
    )
  }
  handle = () => { 
    this.setState({
      name : 'ls'
    })
  }
}
class C1 extends React.Component {
  render () {
    console.log('child1 更新了');
    return (
      <div style={{background:'yellow' }}>
        <p>child1</p>
      </div>
    )
  }
}
class C2 extends React.Component {
  render () {
    console.log('child2 更新了');
    return (
      <div style={{background:'lime' }}>
        <p>child2</p>
      </div>
    )
  }
}
  • 这样效果是出来了,但是它确实有很严重的性能问题, 因为子组件都没有任何变化,如果重新渲染,那么就会重新调用render() 渲染页面, 有损性能
  • 所以需要进行处理 组件性能优化

组件性能优化

优化1: 减轻 state

  • 原则 : state 中 只存储跟组件渲染相关的数据 (比如 : count / 列表数据 等)
  • 不用做渲染的数据 不要放在 state 中, (比如 定时器 id )
// 定时器的 timerId 就 不需要放到state 中,, 只是用来清除 定时器, 和渲染无关
 componentDidMount() {
   // timerId存储到this中,而不是state中
   this.timerId = setInterval(() => {}, 2000)
 }

 componentWillUnmount() {
	clearInterval(this.timerId) 
 }

优化2 : 避免不必要的重新渲染

  • 组件更新机制 : 父组件更新会引起子组件也被更新
  • 问题 : 子组件没有任何变化时, 也会重新渲染
  • 如何避免不必要的重新渲染呢 ?
  • 解决方式 : 使用 钩子函数` shouldComponentUpdate(nextProps, nextState)
    • nextProps : 最新的属性
    • nextState : 最新的状态
    • 场景 : 比较更新前后的 state 或者 props 是否相同, 来决定是否 更新组件
  • 作用 : 通过返回值 决定该组件是否需要重新渲染, 返回true ,表示重新渲染, false 表示不重新渲染
  • 触发时机 : 更新阶段的钩子函数, 组件重新渲染 执行
    • 顺序 : shouldComponentUpdate() ==> render() ==> componentDidMount()
  • 演示 : 点击父组件的计算器的数据count
// 演示1 : 子组件里 通过 shouldComponentUpdate()  { return true or false } 
	- true-更新
	- false-不更新

// 演示2 : 获取 父传过来的属性, 奇数更新,偶数不更新,  
  shouldComponentUpdate(nextProps,nextState)
	  - nextProps : 最新的属性
    - nextState : 最新的状态
 - if(nextState.count % 2 == 0) {
        return false    
     }else { 
        return true
     }

// 演示3 : 就一个组件里有number 值, 点击产生随机值, 比较前后生成的随机值是否一致,不一致就更细, 一致就不需要更新
// count : Math.floor(Math.random()*3)
 shouldComponentUpdate(nextProps,nextState) {
     
     //          上一个的值                  最新的值
     console.log(this.state.count === nextState.count)
     
     // 本次的 nextState.number
     // 上一次的 this.state.number
     //  或者 通过 if..else..判断 
     return this.state.count !== nextState.count
 }

优化3 : 纯组件 - PureComponent

  • 纯组件
  • 作用 : 自动实现了 shouldComponentUpdate() 钩子函数, 不需要再手动对比更新前后的props 或者 state , 来阻止不必要的更新了
  • 原理 : PureComponent 内部, 会别对比更新前后的props 以及更新前后的state , 只要有一个不同, 就会让组件更新, 只有在两者都相同的情况下, 才会阻止组件更新
class Hello extends React.PureComponent {}
// 把那个方法 shouldComponentUpdate() 删除掉, 已经 PureComponent已经封装好了

PureComponent 内部原理

参考 : API Reference => React => React.PureComponent

  • PureComponent

  • 说明 : PureComponent 内部会比较更新前后的 props 和 state 分别进行浅对比

  • 对于简单/值类型来说, 比较两个值是否相同 (直接赋值即可, 没有坑)

// 将 React.Component 替换为: React.PureComponent
class Hello extends React.PureComponent {
  state = {
    // number 就是一个普通的值类型的数据
    number: 0
  }

  handleClick = () => {
    const newNumber = Math.floor(Math.random() * 3)
   console.log(newNum);
    // 对于 值类型 来说,没有任何坑,直接使用即可
    // 更新前的 number:0
    // 更新后的 number:2
    // PureComponent 内部会进行如下对比:
    // 更新前的number === 更新后的number
    //    如果这两个值相同,内部,相当于在 shouldComponentUpdate 钩子函数中返回 false ,阻止组件更新。
    //    如果这两个值不同,内部,相当于在 shouldComponentUpdate 钩子函数中返回 true ,更新组件。
    this.setState({
      number: newNumber
    })
  }

  render() {
    return (
      <div>
        <h1>随机数:{this.state.number}</h1>
        <button onClick={this.handleClick}>随机生成</button>
      </div>
    )
  }
}
  • 对于引用类型来说, 只比较对象的引用(地址) 是否相同
    • 造成的结果 : 对象里 的数据变化,不更新
const { obj } = this.state

let newNum = Math.floor(Math.random() * 3)
console.log(newNum);

// 虽然value 值 也可以从 react-dev-tools 里调试 value值确实都变了,但是 obj 一直没有变
obj.value = newNum

this.setState({
  obj 
})

  • 正确做法 : 根据现有状态生成一个新对象, 然后再更新状态
const newObj = {...this.state.obj} // 创建一个新对象
newObj.value = Math.floor(Math.random() * 3)
this.setState({
    obj: newObj
})
  • 正确做法说明:
    • 在 PureComponent 中 使用引用类型 的状态时, 应该每次都创建一个新的状态, 而不是直接修改当前状态
    • 因为PureComponent 是浅对比, 所以,如果直接修改当前对象中的属性, 会造成: 对象中的值变了, 但是引用地址没有改变, 而导致组件不会被更新, 这样的话就出现bug了
  • 注意 , 在 React 中, ( 不管是PureComponent 还是 Component ) , 都不要直接修改引用类型的状态值, 而是要创建一个新的状态, 修改新的状态,然后再更新

在 React 组件 中更新应用类型的状态

  • 文档 : 不可变数据的力量
  • 注意 : 对于引用类型的状态来说, 应该创建新的状态, 而不要直接修改当前状态
  • 原则 : 状态不可变!!! 数据不要变, 直接创建新的
  • 对象状态 :
// 对象
state = {
	obj : {
		value : 123
	}
}

//ES5
//                            新加     之前的值      修改内容
const newObj = Object.assign( {}, this.state.obj, {value : '新的值'} )
this.setState({
    obj:newObj
})
// es6 
this.setState({
    obj : {...this.state.obj, value: '新的值'}
})
  • 数组状态
// 数组:
state = {
  list: ['a', 'b']
}

// ES5:
this.setState({
  list: this.state.list.concat([ 'c' ]) // ['a', 'b', 'c']
})

// ES6:
this.setState({
  list: [...this.state.list, 'c']
})

// 删除数组元素:[].filter()

虚拟DOM的真正价值

  • 虚拟 DOM 的真正价值从来都不是性能。
  • 真正的价值:虚拟DOM 能够让 React 摆脱浏览器的限制(束缚)。也就是,只要能够运行JS代码的地方,就能够运行 React。
  • 跨平台
    • JSX => 虚拟DOM => react-dom => DOM 元素=> 浏览器
    • JSX => 虚拟DOM => React-Native => ios和安卓的元素 => 移动混合开发
    • JSX => 虚拟DOM => 工具 => VR

React 组件

  • (state, props) => UI

路由基础

路由介绍

  • 路由 : 就是一套映射规则, 是url中 哈希值展示视图 之间的一种对应关系
  • 为什么要学习路由 ?
    • 现代的前端应用大多都是 SPA(单页应用程序),也就是只有一个 HTML 页面的应用程序。
    • 因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。
    • 为了有效的使用单个页面来管理原来多页面的功能,前端路由 应运而生。
  • 使用React路由简单来说,就是配置 路径组件(配对)
spa缺点
 1. 学习成本大 学习路由
 2. 不利于SEO

基本使用

  • 安装 : yarn add react-router-dom
// 1 导入路由中的三个组件
import { BrowerRouter , Link, Route } from 'react-router-dom'

const Hello = () => {
  // 2 使用 BrowerRouter 组件包裹整个应用(才能使用路由)
  return (
    <BrowerRouter>
      <div>
        <h1>React路由的基本使用:</h1>

        {/* 3 使用 Link 组件,创建一个导航菜单(路由入口) */}
        <Link to="/first">页面一</Link>

        {/* 4 使用 Route 组件,配置路由规则以及要展示的组件(路由出口) */}
        <Route path="/first" component={First} />
      </div>
    </BrowerRouter>
  )
}

常用组件的使用介绍

  • 引入的三个组件
// HashRouter => 哈希模式 => 带 #  => 修改的是 location.hash
import { HashRouter , Link, Route } from 'react-router-dom'  //带#
// BrowserRouter => history模式 => 不带 #  => 修改的是 location.pathname
import { BrowserRouter , Link, Route } from 'react-router-dom'  //不带#
  • BrowserRouter 组件 : 使用 Router 组件包裹整个应用 (才能使用路由)
  • Link 组件 : 创建一个导航菜单 (路由入口)
    • 最终会生成一个a标签, 通过 to 属性指定 pathname(history /) 或 hash(哈希模式 #)
  • Route 组件 : 用来配置路由规则和要展示的组件 (路由出口)
    • path : 配置路由规则
    • component : 指定当前路由 规则匹配时要展示的组件
    • Route 组件放在哪, 组件内容就展示在哪, 并且每一个路由都是一个单独的Route组件

路由的执行过程

  • 当点击 Link 的时候,就会修改浏览器中的 pathname
  • 只要 浏览器地址栏中的 pathname 发生改变,React 路由就会监听到这个改变
  • React 路由监听到 pathname 改变后,就会遍历所有 Route 组件,分别使用 Route 组件中的 path 路由规则,与当前的 浏览器地址栏中的pathname进行匹配
  • 只要匹配成功,就会把当前 Route 对应的组件,展示在页面中
  • 注意:匹配时,不是找到第一个匹配的路由就停下来了。而是: 所有的 Route 都会进行匹配,只要匹配就会展示该组件。
    • 也就是说:在一个页面中,可以有多个 Route 同时被匹配

使用 Switch 组件 ,匹配一个

{/* Switch 只会让 组件显示出来一个 */}
<Switch>
  <Route path="/one" component={One}></Route>
	<Route path="/two" component={Three}></Route>
	<Route path="/two" component={Two}></Route>
</Switch>

编程式导航

  • 改变入口的三种方式 :

  • 手动输入

  • 声明式导航 : (html)

    • 编程式导航 : 通过js代码来实现的跳转/返回 (js)
  • 编程式导航 :

  • 可以通过props 拿到 跳转和返回的方法

  • 正常的组件, 打印 props => 默认是 一个空对象 {}

  • 凡是参与路由匹配出来的组件 , 路由都会给他们传入三个属性 history, location, match

  • history : (主要用来编程式导航)

    • push() 跳转到另外一个页面 push(path,state)
    • goBack() 返回上一个页面
    • replace() 跳转到另外一个页面
  • location : (位置路径的)

  • pathname : 路径

    • state : 通过跳转传递的数据
  • match : 获取参数

    • params : 可以拿到动态路由里的参数 params : {id : 123}
 * - push()  跳转到另外一个页面
 * - goBack() 返回上一个页面
 * - replace() 跳转到另外一个页面
 *
 * - push 和 replace 区别
 * - 	push()    跳转 - 记录访问的历史  (可逆)
 * - 	replace() 跳转 - 不记录访问的历史 (不可逆)

 * One :  <button onClick={this.jump}>跳转到two</button> 演示
 * Two :  <button onClick={this.back}>返回到One</button> 返回

HashRouter 传参的方式和 BrowserRouter 传参的方式不一样
 this.props.history.push({
      pathname: '/pay',
      state: {
        name: 'zs'
      }
    })

默认路由 - 根路径 /

  • 默认路由地址为:/
  • 默认路由在进入页面的时候,就会自动匹配
{/* / 表示默认路由规则 */}
<Route path="/" component={Home} />

匹配模式

问题:当 Link组件的 to 属性值为 “/login”时,为什么 默认路由/ 也被匹配成功?

  • 默认情况下,React 路由是: 模糊匹配模式
  • 模糊匹配:只要 pathname 以 path 开头就会匹配成功
    • path 代表Route组件的path属性
    • pathname 代表Link组件的to属性(也就是url中 location.pathname)
  • 精确匹配:只有当 path 和 pathname 完全匹配时才会展示该路由
  • 解决办法 : 给 Route 组件添加 exact 属性,让其变为精确匹配模式
// 添加  exact 之后, 此时,该组件只能匹配 pathname=“/” 这一种情况
<Route exact path="/" component=... />

// 再演示 : /one/two/three 

重定向

  • 需求 : 使用 重定向 '/' => '/one'
  • 方式1 : render-props
<Route exact path='/'
 render={ () => {
   return <Redirect to='/one' />
    }
}></Route>
  • 方式2 - children
<Route exact path='/'>
  <Redirect to='/one' />
</Route>

路由两种模式的说明

哈希模式

 1. 访问路径 :  http://localhost:8080/#/one 
                http://localhost:8080/#/two

 2. 服务器接收到的 (服务器是不会 接收 # 后面的内容的)
 3. 不管访问的路径是什么样的 
     http://localhost:8080  ==> 服务器返回的默认 的就是  index.html

4. 后面的 /one 和 /two 由路由来使用, 根据路由匹配规则找到对应的组件显示
5. 哈希模式 不管是 开发阶段还是发布阶段,都是没有问题的

history 模式

1. 访问路径 :  http://localhost:8080/one 
              http://localhost:8080/two

2. 服务器接收到的 http://localhost:8080/one  和 http://localhost:8080/two
 		但是,/one 和 /two 这个路径是不需要服务器端做任何处理的。
3.  http://localhost:8080/getNews
    http://localhost:8080/detail 
    它们都是 接口地址  , 后面遇到 类似 /one 和 /two 都会以为是接口 是要返回数据的呢?
3. 所以,应该在服务器端添加一个路由配置,直接返回 SPA 的 index.html 页面就行啦。
4. 类似处理
    app.get('/getNews', (req,res) => {
    // 根据 res 返回 对应的数据
      res.json { .....  }
    })
    app.get('/detail', (req,res) => {
     // 根据 res 返回 对应的数据
      res.json { .....  }
    })

    // 最后 额外再多加一个, 专门用来返回 index.html
    app.use('*', (req,res) => {
      res.sendFile('index.html')
    })
    

总结 :

history模式 : 
- 开发阶段 :  webpack脚手架已经处理好了, 
- 发布阶段 : 服务器是公司的服务器, 可能就会报错
- 我们要做的就是`告诉后台`,我们使用的 是 history模式,让他专门处理一下,就可以了
- 如果后台不给处理,或者处理不好, 我们就使用 `哈希模式`

类似 /one 和 /two 都会以为是接口 是要返回数据的呢?
3. 所以,应该在服务器端添加一个路由配置,直接返回 SPA 的 index.html 页面就行啦。
4. 类似处理
app.get(’/getNews’, (req,res) => {
// 根据 res 返回 对应的数据
res.json { … }
})
app.get(’/detail’, (req,res) => {
// 根据 res 返回 对应的数据
res.json { … }
})

// 最后 额外再多加一个, 专门用来返回 index.html
app.use('*', (req,res) => {
  res.sendFile('index.html')
})

#### 总结 : 

```js
history模式 : 
- 开发阶段 :  webpack脚手架已经处理好了, 
- 发布阶段 : 服务器是公司的服务器, 可能就会报错
- 我们要做的就是`告诉后台`,我们使用的 是 history模式,让他专门处理一下,就可以了
- 如果后台不给处理,或者处理不好, 我们就使用 `哈希模式`

你可能感兴趣的:(React,react)