b站上黑马程序员_网页设计与制作黑马程序员

(121) 2024-06-01 23:01:01

视频教程:黑马程序员前端React视频教程,react零基础入门原理详解到好客租房项目实战_哔哩哔哩_bilibili

笔记:

React是一个用于构建用户界面的JavaScript库

用户界面:HTML页面

React主要用来写HTML页面,或构建Web应用

如果从MVC的角度来看,React仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了完整的M和C的功能。

React起源于Facebook的内部项目,后又用来架设Instagram的网站,并于2013年5月开源

React的特点:

声明式

你只需要描述UI看起来是什么样,就跟写HTML一样

基于组件

组件是React最重要的内容

组件表示页面中的部分内容

学习一次,随处使用

使用React可以开发web应用

使用React可以开发移动端原生应用(react-native)

使用React可以开发VR(虚拟现实)应用(react360)

React的安装

安装命令:npm i react react-dom

react包是核心,提供创建元素、组件等功能

react-dom包提供DOM相关功能等

React的使用

<body>

1.引入react和react-dom两个js文件

<script src="./node_modules/react/umd/react.development.js"></script>

<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>

<div id="root"></div>

<script>

 // 2.创建React元素

 // 三个参数分别为:元素名称、元素属性、元素子节点

 const title = React.createElement('h1',null,'Hello React') 

3.渲染React元素到页面中

// 两个参数分别为:要渲染的react元素、挂载点

  ReactDOM.render(title,document.getElementById('root'))

</script>

</body>

React脚手架意义

1.脚手架是开发现代web应用的必备

2.充分利用Webpack、Babel、ESLint等工具辅助项目开发

3.零配置,无需手动配置繁琐的工具即可使用

4.关注业务,而不是工具配置

使用React脚手架初始化项目

1.初始化项目,命令:推荐:npx create-react-app 项目名称

                       也可以使用:npm init react-app 项目名称

                                     或:yarn create react-app 项目名称

2.启动项目,在项目根目录命令:npm start(路径中不能有中文)

npx命令介绍

npm v5.2.0引入的一条命令

目的:提升包内提供的命令行工具的使用体验

原来:先安装脚手架包,再使用这个包中提供的命令

现在:无需安装脚手架包,就可以直接使用这个包提供的命令

在脚手架中使用React

1.导入react和react-dom两个包

import React from 'react'

import ReactDOM from 'react-dom'

2.调用React.createElement()方法创建reate元素。

3.调用ReactDOM.render()方法渲染reate元素到页面中

createElement()的问题

1.繁琐不简洁

2.不直观,无法一看看出所描述的结构

3.不优雅,用户体验不爽

JSX简介

JSX是JavaScript XML的简写,表示在JavaScript代码中写XML(HTML)格式的代码

优势:声明式语法更加直观,与HTML结构相同,降低了学习成本、提升开发效率

JSX是React的核心内容

使用步骤

1.使用JSX语法,创建react元素

const title = <h1>Hello JSX</h1>

2.使用ReactDOM.render()方法渲染react元素到页面中

ReactDOM.render(title,document.getElementById('root'))

为什么脚手架中可以使用JSX语法?

1.JSX不是标准的ECMAScript语法,它是ECMAScript的语法扩展

2.需要使用babel编译处理器后,才能在浏览器环境中使用

3.create-react-app脚手架中已经默认有该配置,无需手动配置

4.编译JSX语法的包为:@babel/preset-react

JSX的注意点

1.React元素的属性名使用驼峰命名法

2.特殊属性名:class->className、for->htmlFor、tabindex->tabIndex

3.没有子节点的React元素可以用/>结束

4.推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱

const dv = (

   <div>Hello JSX</div>

)

嵌入JS表达式

数据存储在JS中

语法:{JavaScript表达式}

注意:语法中是但大括号,不是双大括号

const name = 'Jack'

const dv = (

<div>你好,我叫:{name} </div>

)

JSX中使用JavaScript表达式

注意点

单大括号中可以使用任意的JavaScript表达式

JSX自身也是JS表达式

注意:jS中的对象是一个例外,一般只会style属性中

注意:不能在{}中出现语句(比如:if、for等)

JSX的条件渲染

场景:loading效果

条件渲染:根据条件渲染特定的JSX后结构

可以使用if/else或三元运算符或逻辑与运算符来实现

const isLoading = false

// if-else

const loadData = ()=>{

  if (isLoading){

    return <div>loading ...</div>

  }

  return <div>数据加载完成,此处显示加载之后的数据</div>

}

// 三元表达式

const loadData = ()=>{

return isLoading?( <div>loading ...</div>):( <div>数据加载完成,此处显示加载之后的数据</div>)

}

//逻辑与运算符

const loadData = ()=>{

return isLoading &&( <div>loading ...</div>)}

const title =(

  <h1>

    条件渲染:

    {loadData()}

  </h1>

)

ReactDOM.render(title,document.getElementById('root'))

JSX的列表渲染

如果要渲染一组数据,应该使用数组的map()方法

注意:渲染列表时应该添加key属性,key属性的值要保证唯一

原则:map()遍历谁,就给谁添加key属性

注意:尽量避免使用索引号作为key

const songs = [

{id:1,name:'痴心绝对'},

{id:2,name:'像我这样的人'},

{id:3,name:'南山南'},

]

const list=(

<ul>

{songs.map(item=><li key={item.id}>{item.name}</li>)}

</ul>

)

JSX的样式处理

1.行内样式——style

<h1 style={
{color:'red',backgroundColor:'skyblue'}}>

     JSX的样式处理

</h1>

2.类名——className(推荐)

import './css/index.css'

<h1 className="title"}>

     JSX的样式处理

</h1>

index.css文件里:

.title{

     text-align:center;

}

JSX总结

1.JSX是React的核心内容。

2.JSX表示在JS代码中写HTML结构,是React声明式的体现

3.使用JSX配合嵌入的JS表达式,条件渲染,列表渲染,可以描述任意UI结构

4.推荐使用className的方式给JSX添加样式

5.React完全使用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能

React组件介绍

组件是React的一等公民,使用React就是在用组件

组件表示页面中的部分功能

组合多个组件实现完整的页面功能

特点:可复用,独立,可组合

React组件的两种创建方式

1.使用函数创建组件

函数组件:使用JS的函数(或箭头函数)创建的组件

约定1:函数名称必须以大写字母开头,React据此区分组件和普通的React元素

约定2:函数名称必须有返回值,表示该组件的结构

如果返回值为null,表示不渲染任何内容

渲染函数组件:用函数名作为组件标签名

组件标签可以是单标签也可以是双标签

function Hello(){

     return(

<div>这是我的第一个函数组件!</div>

      )

}

const Hello=()=><div>这是我的第一个函数组件!</div>

ReactDOM.render(<Hello/>,document.getElementById('root'))

2.使用类创建组件

类组件:使用ES6的class创建的组件

约定1:类名称必须以大写字母开头

约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性

约定3:类组件必须提供render()方法

约定4:render()方法必须有返回值,表示该组件的结构

如果返回值为null,表示不渲染任何内容

class Hello extends React.Component{

   render(){

         return <div>这是我的第一个类组件!!</div>

   }

}ReactDOM.render(<Hello/>,document.getElementById('root'))

把组件抽离为独立JS文件

1.创建Hello.js

2.在Hello.js中导入React

3.创建组件(函数或类)

4.在Hello.js中导出该组件

5.在index.js中导入Hello组件

// Hello.js

import React from 'react'

class Hello extends React.Component{

   render(){

         return <div>这是我的第一个类组件!!</div>

   }

}

// 导出Hello组件

export default Hello

//inedx.js

import Hello from '/.Hello'

//渲染导入的Hello组件

ReactDOM.render(<Hello/>,document.getElementById('root'))

事件绑定

React事件绑定语法与DOM事件语法相似

语法:on+事件名称=(事件处理程序),比如:onClick={()=>{}}

注意:React事件采用驼峰命名法,比如:onMouseEnter、onFocus

// 类组件事件绑定

class App extends React.Component{

   handleClick(){

        console.log('单击事件触发了')

   }

   render(){

         return(

               <button onClick={this.handleClick}>点我</button>

          )

    }

}

// 函数组件事件绑定

function App(){

     function handleClick(){

            console.log('单击事件触发了')

     }

     return(

               <button onClick={handleClick}>点我</button>

      )

}

事件对象

可以通过事件处理程序的参数获取到事件对象

React中的事件对象叫做:合成事件(对象)

合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题

 function handleClick(e){

       // 组织浏览器的默认行为

       e.preventDefault()

       console.log('事件对象',e)

}

<a onClick={handleClick}>点我,不会跳转页面</a>

有状态组件和无状态组件

函数组件又叫做无状态组件,类组件又叫做有状态组件

状态(state)即数据

函数组件没有自己的状态,只负责数据展示(静)

类组件有自己的状态,负责更新UI,让页面动起来

组件中的state和setState

state的基本使用

状态(state)即数据,是组件内部的私有数据,只能在组件内部使用

state的值是对象,表示一个组件中可以有多个数据

获取状态:this.state

状态是可变的

语法:this.setState({要修改的数据})

setState()作用:1.修改state2.更新UI

思想:数据驱动视图

class Hello extends React.Component{

   constructor(){

      // 原本语法:

        super()

        // 初始化state

        this.state={

               count:0

        }

      //简化语法(推荐 )

      state={

               count:0

        }

      render(){

             return(

                   <div>计数器:{this.state.count}</div>

                   <button onClick={()=>{

                          this.setState({

                                 count:this.state.count+1

                          })

                   }}>+1</button>

                   </div>

             )

     }

}

从JSX中抽离事件处理程序

JSX中掺杂过多JS逻辑代码,会显得非常混乱

推荐:将逻辑抽离到单独的方法中,保证JSX逻辑清晰

class Hello extends React.Component{

   constructor(){

          //简化语法(推荐 )

      state={

               count:0

        }

onIncrement(){

         this.setState({

                     count:this.state.count+1

          })

}

      render(){

             return(

                   <div>计数器,{this.state.count}</div>

                   <button onClick={()=>this.onIncrement}>+1</button>

                   </div>

             )

     }

}

事件绑定this指向

1.箭头函数

利用箭头函数自身不绑定this的特点

class Hello extends React.Component{

 onIncrement(){

         this.setState({   ……  })

}

      render(){

            //箭头函数中的this指向外部环境,此处为:render()方法

             return(

                  <button onClick={()=>this.onIncrement}></button>

             )

     }

}

2.Function.prototype.bind()

利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定在一起

class Hello extends React.Component{

      constructor(){

                super()

                this.onIncrement=this.onIncrement.bind(this)

       }

       //…省略onIncrement

       render(){

              return(

                     <button onClick={this.onIncrement}></button>

               )

       }

}

3.class的实例方式(推荐)

利用箭头函数形式的class实例方法

注意:该语法是实验性语法,但是,由于babel的存在可以直接使用

class Hello extends React.Component{

      onIncrement=()=>{

                 this.setState({……})

      render(){

              return(

                     <button onClick={this.onIncrement}></button>

               )

       }

}

表单处理

受控组件

HTML 中的表单元素是可输入的,也就是有自己的可变状态

而,React中可变状态通常保存在state中,并且只能通过setState()方法来修改

React将state 与表单元素值value绑定到一起,由state的值来控制表单元素的值

受控组件∶其值受到 React控制的表单元素

步骤:

1.在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)

2.给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)

文本框:

class App extends React.Component{

  state={txt:''}

  handleChange=e=>{

      this.setState({

         txt:e.target.value

     })

   }

   render(){

     return(

         <div>

             <input type="text" value={this.state.txt} onChange={this.handleChange}/>

         </div>

     )

   }

}

ReactDOM.render(<App/>,document.getElementById('root'))

富文本框:

class App extends React.Component{

  state={content:''}

  handleChange=e=>{

      this.setState({

         city:e.target.value

     })

   }

   render(){

     return(

         <div>

             <textarea value={this.state.content} onChange={this.handleChange}></textarea>

         </div>

     )

   }

}

ReactDOM.render(<App/>,document.getElementById('root'))

下拉框:

class App extends React.Component{

  state={city:'bj'}

  handleChange=e=>{

      this.setState({

         content:e.target.value

     })

   }

   render(){

     return(

         <div>

            <select value={this.state.city} onChange={this.handleChange}>

                                     <option value="sh">上海</option>

                                     <option value="bj">北京</option>

                                     <option value="gz">广州</option>

                             </select>

         </div>

     )

   }

}

ReactDOM.render(<App/>,document.getElementById('root'))

复选框:

class App extends React.Component{

  state={isChecked:false}

  handleChange=e=>{

      this.setState({

         isChecked:e.target.checked

     })

   }

   render(){

     return(

         <div>

            <input type="checkbox" checked={this.state.isChecked} onChange={this.handleChange}/>

         </div>

     )

   }

}

ReactDOM.render(<App/>,document.getElementById('root'))

多表单元素优化步骤:

1.给表单元素添加name属性,名称与state相同

<input

     type="text"

     name="txt"

     value={this.state.txt}

     onChange={this.handleForm}

/>

2.根据表单元素类型获取对应值

const value = target.type==='checkbox'

       ?target.checked

        :target.value

this.setState({

        [name]:value

})

3.在change事件处理程序中通过[name]来修改对应的state

非受控组件

借助于ref,使用原生DOM方式来获取表单元素值

ref的作用:获取DOM或组件

使用步骤:

1.调用ReacteRef()方法创建一个ref对象

constructor(){

    super()

    this.txtRef = React.createRef()

}

2.将创建好的ref对象添加到文本框中

<input type="text" ref={this.txtRef}>

3.通过ref对象获取到文本框的值

Console.log(this.txtRef.current.value)

案例:

b站上黑马程序员_网页设计与制作黑马程序员 (https://mushiming.com/)  第1张

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

class Hello extends React.Component{

  state={

      pinglun:[],

      userName:"",

      userContent:""

}

    handleChange=e=>{

      const {name,value}= e.target

      this.setState({

        [name]:value,

    })

    }

    handleButton=()=>{

      const{userName,userContent,pinglun}=this.state

      const newContent=[{

        id:this.state.pinglun.length+1,

        Commentator:userName,

        content:userContent},...pinglun]

        if ( userName===""||userContent==="") {

          alert("请输入评论人姓名及评论内容")

        }else{

      this.setState({

      pinglun:newContent,

      userName:"",

      userContent:""

      }

      )}

    }

  render(){

        return <div className='border'>

          <input className="input" value={this.state.userName} name="userName" placeholder="请输入评论人" onChange={this.handleChange}/>

          <textarea className="textarea"  name="userContent" value={this.state.userContent} placeholder="请输入评论内容" onChange={this.handleChange}/>

          <button className="button" onClick={this.handleButton}>发表评论</button>

          <div >{

          this.state.pinglun.length===0?(<div className='pinglun'>暂无评论,快去评论吧~</div> )

                                   🙁 <div></div>)

            }

            <ul>

              {this.state.pinglun.map(item=>(

                <li key={item.id}>

                  <h3>评论人:{item.Commentator}</h3>

                  <div>评论内容:{item.content}</div>

                </li>

              ))}

            </ul>

          </div>

        </div>

  }

}ReactDOM.render(<Hello/>,document.getElementById('root'))

组件通信介绍

组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据,在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好地完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯。

组件的props

组件是封闭的,要接收外部数据应该通过props来实现

props的作用:接收传递给组件的数据

传递数据:给组件标签添加属性

接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

函数组件:

function Hello(props){

    console.log(props)

    return(

        <div>接收到数据:{props.name}</div>

    )

}

 <Hello name="jack" age={19}/>

类组件:

class Hello extends React.Component{

      render(){

             return(

                   <div>接收到数据:{props.name}</div>

             )

      }

}

 <Hello name="jack" age={19}/>

组件的props

特点

1.可以给组件传递任意类型的数据

2.props是只读的对象,只能读取属性的值,无法修改对象

3.注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props

class Hello extends React.Component{

      constructor(props){

           super(props)

       }

       render(){

              return<div>接受到的数据:{this.props.age}</div>

       }

}

组件通讯的三种方式

1.父组件—>子组件

2.子组件—>父组件

3.兄弟组件

父组件传递数据给子组件

1.父组件提供要传递的state数据

2.给子组件标签添加属性,值为state中的数据

3.子组件中通过props接收父组件中传递的数据

父组件:

class Parent extends React.Component{

      state = {lastName:'王'}

     render(){

           return(

                 <div>

                       传递数据给子组件:< Child name={this.state.lastName}/>

                  </div>

           )

      }

}

子组件:

function Child(props){

      return <div>子组件接收到数据:{prop.name}</div>

}

子组件传递数据给父组件

思路:利用回调函数,父组件提供回调函数,子组件调用,将要传递的数据作为回调函数的参数

1.父组件提供一个回调函数(用于接收数据)

2.将该函数作为属性的值,传递给子组件

class Parent extends React.Component{

      getChildMsg = (msg) =>{

            console.log('接收到子组件数据',msg)

       }

       render(){

            return(

                    <div>

                          子组件:<Child getMsg={this.getChildMsg}/>

                    </div>

            }

       }

}

3.子组件通过props调用回调函数

4.将子组件的数据作为参数传递给回调函数

class Child extends React.Component{

      state = { childMsg:'React'}

      handleClick = () =>{

            this.props.getMsg(this.state.childMsg)

       }

       render(){

            return(

                  <button onClick = {this.handleClick}点我,给父组件传递数据</button>          

            )

       }

}

兄弟组件

将共享状态提升到最近的公共组件中,由公共父组件管理这个状态

思想:状态提升

公共父组件职责:1.提供共享状态2.提供操作共享状态的方法

要通讯的子组件只需通过props接收状态或操作状态的方法

//父组件

class Counter extends React.Component{

// 提供共享状态

state = {

      count:0

}

// 提供修改状态的方法

onIncrement=()=>{

     this.setState({

            count:this.state.count+1

      })

}

      render(){

               return(
                     <div>

                             <Child1 count={this.state.count}/>

                             <Child2 onIncrement={this.onIncrement}/>

                     </div>

            )

      }

}

const  Child1 = props=>{

      return <h1>计数器:{props.count}</h1>

}

const  Child2 = ( )=>{

      return <button onClick={()=>props.onIncrement()}>+1</button>

}

ReactDOM.render(<Counter />,document.getElementById('root'))

Context

作用:跨组件传递数据

如果两个组件是远房亲戚(比如,嵌套多层)可以使用Context实现组件通讯

Context提供了两个组件:Provider和Consumer

Provider组件:用来提供数据

Consumer组件:用来消费数据

使用步骤:

1.调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件

const{Provider,Consumer}=React.createContext()

2.使用Provider组件作为父节点

3.设置value属性,表示要传递的数据

<Provider  value="pink">

     <div className="APP">

            <Child1 />

     </div>

</Provider>

4.调用Consumer组件接收数据

<Consumer>

    {data =><span>data参数表示接收到的数据——{data}</span>}

</Consumer>

Children属性

表示组件标签的子节点。当组件标签有子节点时,propa就会有该属性

children属性与普通的props一样,值可以是任意值

function Hello(props){

      return(
           <div>

                  组件的子节点:{props.children}

            </div>

     )

}

<Hello>我是子节点</Hello>

props校验

对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据

如果传入的数据格式不对,可能会导致组件内部报错

关键问题:组件的使用者不知道明确的错误原因

props校验:允许在创建组件的时候,就指定props的类型、格式等

作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组建的健壮性

使用步骤

1.安装包prop-type(yarm add prop-types/npm i prop-types)

2.导入prop-types包

import PropTypes from 'prop-types'

3.使用组件名.propTypes={}来给组件的props添加校验规则

App.propTypes={

       colors:PropTypes.array

}

4.校验规则通过PropTypes对象来指定

约束规则:

1.创建类型:array、bool、func、number、object、string

 colors:PropTypes.array

2.React元素类型:element

 colors:PropTypes.element

3.必填项:isRequired

 colors:PropTypes.isRequired

4.特定结构的对象:shape({})

 colors:PropTypes.shape({

       first:PropTypes.array,

       second:PropTypes.number

})

props的默认值

场景:分页组件=>每页显示条数

作用:给props设置默认值,在未传入props时生效

function App(props){

       return(

             <div>

                   此处展示props的默认值:{props.pageSize}

             </div>

        )

}

// 设置默认值

App.defaultProps={

       pageSize:10

}

// 不传入pageSize属性

<App/>

组件的生命周期

组件的生命周期概述

意义:组件的生命周期有助于理解组件的运行方式,完成更复杂的组件功能,分析组件错误原因等

组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程

生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数

钩子函数的作用:为开发人员在不同阶段操作组件提供了时机

只有类组件才有生命周期

生命周期的三个阶段

1.每个阶段的执行时机

2.每个阶段钩子函数的执行顺序

3.每个阶段钩子函数的作用

b站上黑马程序员_网页设计与制作黑马程序员 (https://mushiming.com/)  第2张

1.创建时(挂载阶段)

执行时机:组件创建时(页面加载时)

执行顺序:constructor()=>render()=>componentDidMount

钩子函数

触发时机

作用

constructor

创建组件时,最先执行

1.初始化state

2.为事件处理程序绑定this

render

每次组件渲染都会触发

渲染UI(注意:不能调用setState)

componentDidMount

组件挂载(完成DOM渲染)后

1.发送网络请求

2.DOM操作

2.更新时

执行时机:1.setStare()2.forceUplate()3. 组件接收到新的props

说明:以上三者任意一种变化,组件就会重新渲染

执行顺序:render()=>componentDidMount

钩子函数

触发时机

作用

render

每次组件渲染都会触发

渲染UI(与挂载阶段是同一个render)

componentDidMount

组件更新(完成DOM渲染)后

1.发送网络请求

2.DOM操作

3.注意:如果要setState() 必须放在一个if条件中

3.卸载时(卸载阶段)

执行时机:组件从页面中消失

componentWillUnMount

组件卸载(从页面中消失)

执行清理工作(比如:清理定时器等)

render-props和高阶组件

React组件复用概述

两种方式:1.render prop模式2.高阶组件(HOC)

注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)

render-props模式

思路分析

思路:将要复用的state和操作state的方法封装到一个组件中

问题:如何拿到该组件中复用的state?

在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)

问题:如何去渲染任意的UI?

使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)

<Mouse render={(mouse)=>(

         <p>鼠标当前位置{mouse.x},{mouse.y}</p>

)}/>

使用步骤

1.创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态2.操作状态的方法)

2.将要复用的状态作为props.render(state)方法的参数,暴露到组件外部

3.使用props.render()的返回值作为要渲染的内容

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

class Mouse extends React.Component{

  state={

    x:0,

    y:0

  }

  // 鼠标移动事件的事件处理程序

  handleMouseMove=e=>{

    this.setState({

      x:e.clientX,

      y:e.clientY,

    })

  }

  //监听鼠标移动事件

  componentDidMount(){

    window.addEventListener('mousemove',this.handleMouseMove)

  }

  render(){

    return this.props.render(this.state)

  }

}

class Hello extends React.Component{

  render(){

    return(

      <div>

        <h1>render props模式</h1>

        <Mouse

        render={mouse=>{

          return (<p>鼠标位置:{mouse.x},{mouse.y}</p>)

        }}/>

        </div>

    )

  }

}ReactDOM.render(<Hello/>,document.getElementById('root'))

Mouse组件的复用

// 导入图片资源

import img from '...'

<Mouse render={mouse=>{

      return <img src={img} alt="猫" style={
{

       position:"absolute",

       top:mouse.y,

       left:mouse.x

   }}/>

}}></Mouse>

children代替render属性

注意:并不是该模式叫render props就必须使用名为render的prop,实际上可以使用任意名称的prop

把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式

推荐:使用children代替render属性

<Mouse>

     {mouse=>

           renter(

                <p>鼠标的位置是{x},{y}</p>

            )

      }

</Mouse>

//组件内部:

this.props.children(this.state)

//Context中的用法:

<Consumer>

         {data=><span>data参数表示接收到的数据——{data}</span>}

</Consumer>

代码优化

1.推荐:给render props模式添加props校验

Mouse.propTypes={

    children:PropTypes.func.isRequired

}

2.应该在组件卸载时接触mousemove事件绑定

componentWillUnmount(){

       window.removeEventListener('mousemove',this.handleMouseMove)

}

高阶组件

概述

目的:实现状态逻辑复用

采用包装(装饰)模式。比如说:手机壳

手机:获取保护功能

手机壳:提供保护功能

高阶组件(HOC)就相当于手机壳,通过包装组件,增强组件功能

思路分析

高阶组件是一个函数,接收要包装的组件,返回增强后的组件

高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件

const EnhancedComponent = withHOC(WrappedComponent)

//高阶组件内部创建的类组件:

class Mouse extends React.Component{

         render(){

                  return <WrappedComponent{…this.state}/>

          }

 }

使用步骤

1.创建一个函数,名称约定以with开头

2.指定函数参数,参数应该以大写字母开头(作为要渲染的组件)

3.在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回

4.在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件

5.调用该高阶组件,传入要增强的组件,并将其渲染到页面中去

function withMouse(WrappddComponent){

      class Mouse extends React.Component{}

        return Mouse

Mouse组件的render方法中:

return<WrappedComponent{…this.state}>

// 创建组件

const MousePosition = withMouse(Position)

// 渲染组件

<MousePosition/>

设置displayName

使用高阶组件存在的问题:得到的两个组件名称相同

原因:默认情况下,React使用组件名称作为displayName

解决方式:为高阶组件设置displayName便于调式时区分不同的组件

displayName作用:用于设置调试信息(React Developer Tools信息)

设置方式:

Mouse.displayName  = `WithMouse${getDisplayName(WrappedComponent)}`

function getDisplayName(WrappedComponent){

        return WrappedComponent.displayName||WrappedComponent.name||'Component'

}

传递props

问题:props丢失

原因:高阶组件没有往下传递props

解决方式:渲染WrappedComponent时,将state和this.props一起传递给组件

传递方式:

<WrappedComponent {…this.state}{…this.props}/>

React组件进阶总结

1.组件通讯是构建React应用必不可少的一环。

2.props的灵活性让组件更加强大

3.状态提升是React组件的常用模式

4.组件生命周期有助于理解组件的运行过程

5.钩子函数让开发者可以在特定的时机执行某些功能

6.render props模式和高阶组件都可以实现组件状态逻辑复用

7.组件极简模型:(state,props)=>UI

setState()的说明

更新数据

setState()是异步更新数据的

注意:使用该语法时,后面的setState()不要依赖于前面的setState()

可以多次调用setState(),只会触发一次重新渲染

推荐语法

推荐:使用setState((state,props)=>())

参数state:表示最新的state

参数props:表示最新的props

注意:这种方法也是异步更新

第二个参数

场景:在状态更新(页面完成重新渲染)后立即执行某个操作

语法:setState(updater[,callback])

this.setState(

       (state,props)=>{},

       ()=>{console.log('这个回调函数会在状态更新后立即执行')}

)

JSX语法的转化过程

JSX仅仅是createElement()方法的语法糖(简化语法)

JSX语法被@babel/preset-react插件编译为createElement()方法

createElement()方法又被转化为React元素

React元素:是一个对象,用来描述你希望在屏幕上看到的内容

组件更新机制

setState()的两个作用:1.修改state2.更新组件(UI)

过程:父组件重新渲染,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)

组件性能优化

减轻state:只存储跟组件渲染相关的数据(比如:count/列表数据/loading等)

注意:不用做渲染的数据不要放在state中,比如定时器id等

对于这种需要在多个方法中用到的数据,应该放在this中

class Hello extends Component{

       componentDidMount(){

                   // timerID存储到this中,而不是state中

              this.timerId=setInterval(()=>{},2000)

      }

      componentWillUnmount(){

                clearInterval(this.timerId)

       }

       render(){…}

}

避免不必要的重新渲染

组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰

问题:子组件没有任何变化时也会重新渲染

如何避免不必要的重新渲染

解决方式:使用钩子函数shouldComponentUpdate(nextProps,nextState)

作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染

触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpate=>render)

class Hello extends Component{

        shouldComponentUpate(){

                   //根据条件,决定是否重新渲染组件

              return false

          }

          render()   {…}

}

例子:生成随机数

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

class Hello extends React.Component{

  state={

    number:0

  }

handleClick=()=>{

  this.setState(()=>{

    return {

      number:Math.floor(Math.random()*3)

    }

  })

}

shouldComponentUpdate(nextProps,nextState){

    console.log('最新状态:',nextState,',当前状态: ',this.state)

    return nextState.number !== this.state.number

}

 render(){

  return(

    <div>

      <h1>随机数:{this.state.number}</h1>

      <button onClick={this.handleClick}>重新生成</button>

    </div>

  )

 }

}ReactDOM.render(<Hello/>,document.getElementById('root'))

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

class Hello extends React.Component{

  state={

    number:0

  }

handleClick=()=>{

  this.setState(()=>{

    return {

      number:Math.floor(Math.random()*3)

    }

  })

}

 render(){

  return(

    <div>

      <NumberBox number={this.state.number}/>

      <button onClick={this.handleClick}>重新生成</button>

    </div>

  )

 }

}

class NumberBox extends React.Component{

  shouldComponentUpdate(nextProps){

    return nextProps.number!==this.props.number

  }

  render(){

    return(<h1>随机数:{this.props.number}</h1>)

  }

}

ReactDOM.render(<Hello/>,document.getElementById('root'))

纯组件

PureComponent与React.Component功能相似

区别:PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动对比

原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件

class Hello extends React.PureComponent{

       render(){

              return(

                     <div>纯组件</div>

               )

         }

}

说明:纯组件内部的对比是shallowCompare(浅层对比)

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

let number = 0

let newNumber = number

newNumber = 2

console.log(number === newNumber) // false

state ={number:0}

setState({

     number:Math.floor(Math.random()*3)

})

// PureComponent内部对比:

最新的state.number === 上一次的state.number// false,重新渲染组件

对于引用类型来说:只比较对象的引用(地址)是否相同

注意:state或props中属性值为引用类型时,应该创建新数据,不要直接修改原数据

const obj = { number:0 }

const newObj = obj

newObj.number = 2

console.log(newObj ===obj) //true

state ={obj:{number:0}}

// 错误做法

state.obj,number = 2

setState({obj:state.obj})

// PureComponent内部对比:

最新的state.obj === 上一次的state.obj// true,重新渲染组件

//正确!创建新数据

const newObj = {…state.obj,number:2}

setState({obj:newObj})

//正确!创建新数据

// 不要用数组的push/unshift等直接修改当前数组的方法

// 而应该用const 或slice等这些返回新数组的方法

this.setState({

     list:[…this.state.list,{新数据}]

})

虚拟DOM和Diff算法

React更新视图的思想是:只要state变化就重新渲染视图

特点:思路非常清晰

问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是

理想状态:部分更新,只更新变化的地方

问题:React是如何做到部分更新的?虚拟DOM和Diff算法

虚拟DOM:本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)

执行过程

1.初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)

2.根据虚拟DOM生成真正的DOM,渲染到页面中

3.当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)

4.与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容

5.最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面

React 原理揭秘总结

1.工作角度:应用第一,原理第二

2.原理有助于更好地理解React的自身运行机制

3.setState()异步更新数据

4.父组件更新导致子组件更新,纯组件提升性能

5.思路清晰简单为前提,虚拟DOM和Diffb保效率

6.虚拟DOM=>state+JSX

7.虚拟DOM的真正价值从来都不是性能

React路由介绍

现代的前端应用大多数都是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生

前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)

前端路由是一套映射规则,在React中,是URL路径与组件的对应关系

使用React路由简单来说,就是配置路径和组件(配对)

使用步骤

1.安装:yarn add react-router-dom/npm i react-router-dom@5.2.0

2.导入路由的三个核心组件:Router/Route/Link

import{ErowserRouter as Router,Route,Link}from 'react-router dom'

3.使用Router组件包裹整个应用(重要)

<Router>

    <div className="APP">

            // …省略页面内容

    </div>

</Router>

4.使用Link组件作为导航菜单(路由入口)

<Link to ="/first">页面一</Link>

5.使用Route组件路由规则和要展示的组件(路由出口)

const First =()=><p>页面一的页面内容</p>

<Router>

      <div className="APP">

               <Link to="/first">页面一</Link>

               <Route path="/first" component={First}></Route>

      </div>

</Router>

常用组件说明

Route组件:包裹整个应用,一个React应用只需要使用一次
两种常用Router:HashRouter和BrowserRouter

HashRouter:使用URL的哈希值实现(localhost:3000/#/first)

(推荐)BrowserRouter:使用H5的historyAPI实现(localhost:3000/first)

Link组件:用于指定导航链接(a标签)

// to属性:浏览器地址栏中的pathname(location.pathname)

<Link to="/first">页面一</Link>

Route组件:指定路由展示组件相关信息

//path属性:路由规则

// component属性:展示的组件

// Route组件写在哪,渲染出来的组件就展示在哪

<Route path="/first" component={First}></Route>

路由的执行过程

1.点击Link组件(a标签),修改了浏览器地址栏中的url

2.React路由监听到地址栏url的变化

3.React路由内部遍历所有Route组件,使用路由规则(path与)pathnane进行匹配

4.当路由规则(path)能够匹配地址栏中的pathname时,就展示该Route组件的内容

编程式导航

场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如果实现?

编程式导航:通过JS代码来实现页面跳转

history是React路由提供的,用于获取浏览器历史记录的相关信息

push(path):跳转到某个页面,参数path表示要跳转的路径

go(n):前进或后退到某个页面,参数n表示前进或后退页面数量(比如:-1表示到上一页)

class Login extends Component{

       handleLogin = ()=>{

                // . . .

                this.props.history.push('/home')

           

        }

        render(){…省略其他代码}

}

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

import {BrowserRouter as Router,Route,Link} from 'react-router-dom'

class Login extends React.Component{

  handleLogin=()=>{

    //使用编程式导航来实现显示路由

    this.props.history.push('/home')

  }

 render(){

  return(

    <div>

     <p>登录页面:</p>

     <button onClick={this.handleLogin}>登录</button>

    </div>

  )

 }

}

const Home =props=> {

  const handleBack=()=>{

    //go(-1)表示返回上一个页面

    props.history.go(-1)

  }

  return(

    <div>

      <h2>我是后台首页</h2>

      <button onClick={handleBack}>返回登陆页面按钮</button>

    </div>

    )

  }

const App=()=>(

  <Router>

    <div>

      <h1>

        编程式导航:

      </h1>

      <Link to="/login">去登陆页面</Link>

      <Route path="/login" component={Login}/>

      <Route path='/home' component={Home}/>

      </div>

  </Router>

)

ReactDOM.render(<App/>,document.getElementById('root'))

默认路由

问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?

默认路由:表示进入页面时就会匹配路由

默认路由path为:/

<Route path="/" component={Home}/>

匹配模式

模糊匹配模式

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

默认情况下,React路由是模糊匹配模式

模式匹配规则:只要pathname以path开头就会匹配成功

<Link to="/login">登录页面</Link>

<Route path="/" component={Home}/>匹配成功

// path代表Route组件的path属性

// pathname代表Link组件的to属性(也就是location.pathname)

path

能够匹配的pathname

/

所有pathname

/first

/first或/first/a或/first/a/b

 精确匹配

问题:默认路由任何情况下都会展示,如何避免这种问题?

给Route组件添加exact属性,让其变为精确匹配模式

精确匹配:只有当path和pathname完全匹配时才会展示该路由

// 此时,该组件只能匹配pathname="/"这一种情况

<Route exact path="/" component=…>

推荐:给默认路由添加exact属性

React路由基础总结

1.React路由可以有效的管理多个视图(组件)实现SPA

2.Router组件包裹整个应用,只需要使用一次

3.Link组件是入口,Route组件是出口

4.通过props.history实现编程式导航

5.默认模糊匹配,添加exact变精确匹配

6.React路由的一切都是组件,可以像思考组件一样思考路由

 

THE END

发表回复