React入门到实战(自学版)


文章目录


前言

优秀的判断力来自经验,但经验来自于错误的判断。 –Fred Brooks


一、React介绍

1. React起源与发展

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

2. React与传统MVC的关系

轻量级的视图层库!A JavaScript library for building user interfaces

React不是一个完整的MVC框架,最多可以认为是MVC中的V(View),甚至React并不非常认可MVC开发模式;React 构建页面 UI 的库。可以简单地理解为,React 将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

3. React的特性

在这里插入图片描述

4. 虚拟DOM

在这里插入图片描述

二、create-react-app

全局安装create-react-app

$ npm install -g create-react-app

创建一个项目

$ create-react-app your-app 注意命名方式

Creating a new React app in /dir/your-app.
Installing packages. This might take a couple of minutes. 安装过程较慢,
Installing react, react-dom, and react-scripts...

如果不想全局安装,可以直接使用npx

$ npx create-react-app myapp 也可以实现相同的效果

这需要等待一段时间,这个过程实际上会安装三个东西

  1. react: react的顶级库
  2. react-dom: 因为react有很多的运行环境,比如app端的react-native, 我们要在web上运行就使用 react-dom
  3. react-scripts: 包含运行和打包react应用程序的所有脚本及配置

出现下面的界面,表示创建项目成功:

Success! Created your-app at /dir/your-app
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm run build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm run eject
Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
cd your-app npm start
Happy hacking!

根据上面的提示,通过cd your-app 命令进入目录并运行npm start 即可运行项目。生成项目的目录结构如下:

├── README.md 使用方法的文档
├── node_modules 所有的依赖安装的目录
├── package-lock.json 锁定安装时的包的版本号,保证团队的依赖能保证一致。
├── package.json
├── public 静态公共目录
└── src 开发用的源代码目录

常见问题:

npm安装失败

  • 切换为npm镜像为淘宝镜像
  • 使用yarn,如果本来使用yarn还要失败,还得把yarn的源切换到国内
  • 如果还没有办法解决,请删除node_modules及package-lock.json然后重新执行npm install命令
  • 再不能解决就删除node_modules及package-lock.json的同时清除npm缓存npm cache clean --force 之后再执行npm install 命令

三、编写第一个react应用程序

react开发需要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生产版本,create-react-app里已经帮我们把这些东西都安装好了。把通过CRA创建的工程目录下的src目录清空,然后在里面重新创建一个index.js. 写入以下代码:

// 从 react 的包当中引入了 React。只要你要写 React.js 组件就必须引入React, 因为react里有一种语法叫JSX,稍后会讲到JSX,要写JSX,就必须引入React
import React from 'react'
// ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。它是从 react-dom 中引入的,而不是从 react 引入。
import ReactDOM from 'react-dom'
// ReactDOM里有一个render方法,功能就是把组件渲染并且构造 DOM 树,然后插入到页面上某个特定的元素上
ReactDOM.render(
// 这里就比较奇怪了,它并不是一个字符串,看起来像是纯 HTML 代码写在 JavaScript 代码里面。语法错误吗?这并不是合法的 JavaScript 代码, “在 JavaScript 写的标签的”语法叫 JSX-JavaScript XML。
<h1>欢迎进入React的世界</h1>,
// 渲染到哪里
document.getElementById('root')
)

在这里插入图片描述

注意
<React.StrictMode> 目前有助于:
识别不安全的生命周期
关于使用过时字符串 ref API 的警告
检测意外的副作用
检测过时的 context API

官方推荐插件
在这里插入图片描述

安装 React DevTools 之后,右键点击页面的任何一个元素,然后选择“查看”,这样就能打开浏览器的开发者工具了,并且工具栏最后会多展示一个 React 的选项卡(包含 “⚛️ Components” 和 “⚛️ Profiler”)。你可以使用 “⚛️ Components” 来检查组件树。

不过,如果你使用的是 CodePen 在线编辑器的话,还需要几步操作才能正确使用开发工具

登录或注册,然后在邮件中确认(需要关闭垃圾邮件)。
点击 “Fork” 按钮。
点击 “Change View”,然后选择 “Debug mode”。
上一步会打开一个新的标签页,此时开发者工具就会有一个 React 标签了。

四.JSX语法与组件

1. JSX语法

JSX 将 HTML 语法直接加入到 JavaScript 代码中,再通过翻译器转换到纯 JavaScript 后由浏览器执行。在实际开发中,JSX 在产品打包阶段都已经编译成纯 JavaScript,不会带来任何副作用,反而会让代码更加直观并易于维护。 编译过程由Babel 的 JSX 编译器实现。

https://reactjs.org/docs/hello-world.html

原理:
要明白JSX的原理,需要先明白如何用 JavaScript 对象来表现一个 DOM 元素的结构?看下面的DOM结构

<div class='app' id='appRoot'>
    <h1 class='title'>欢迎进入React的世界</h1>
    <p>
        React.js 是一个帮助你构建页面 UI 的库
    </p>
</div>

上面这个 HTML 所有的信息我们都可以用 JavaScript 对象来表示:

{
	tag: 'div',
	attrs: { className: 'app', id: 'appRoot'}, children: [
		{
			tag: 'h1',
			attrs: { className: 'title' }, children: [' 欢迎进入React的世界 ']
		},
		{
			tag: 'p', attrs: null,
			children: ['React.js 是一个构建页面 UI 的库']
		}
	]
}

但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。

于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX结构转换成 JavaScript 的对象结构。

下面代码:

import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component { render () { return (

<div className='app' id='appRoot'>
    <h1 className='title'>欢迎进入React 的世界</h1>
    <p>
        React.js 是一个构建页面 UI 的库
    </p>
</div>
) } } ReactDOM.render(
<App />, document.getElementById('root') )

编译之后将得到这样的代码:


import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component { render () {
    return ( React.createElement(
    "div"
        {
        className: 'app', id: 'appRoot'
        },
            React.createElement( "h1",
                { className: 'title' }, "欢迎进入React 的世界"
                ),
                React.createElement( "p",
                null,
                "React.js 是一个构建页面 UI 的库"
                )
            )
        )
    }
}
ReactDOM.render( React.createElement(App),
document.getElementById('root')
)
React.createElement

会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、
还有子元素等, 语法为

React.createElement( type,[props], [...children])

所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:

JSX —使用react构造组件,bable进行编译—> JavaScript对象 — ReactDOM.render() —>DOM元素 —>插入页面

2. Class组件

ES6的加入让JavaScript直接支持使用class来定义一个类,react创建组件的方式就是使用的类的继承,
ES6 class 是目前官方推荐的使用方式,它使用了ES6标准语法来构建,看以下代码:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
    render () {
        return (<h1>欢迎进入React的世界</h1>)
    }
}
ReactDOM.render(<App />, document.getElementById('root'))

es6 class 组件其实就是一个构造器,每次使用组件都相当于在实例化组件,像这样:

import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (<h1>欢迎进入 {
    this.props.name
    }
    的世界 </h1>)
}
}
const app=new App( {
name: 'react'
}
).render()
 ReactDOM.render(app,
document.getElementById('root'))

3. 函数式组件

import React from 'react'
import ReactDOM from 'react-dom'
const App = (props) => <h1> 欢迎进入React的世界</h1> ReactDOM.render(
	// React组件的调用方式
	<App />, document.getElementById('root')
)

这样一个完整的函数式组件就定义好了。但要注意!注意!注意!组件名必须大写,否则报错。

4. 组件的样式

  • 行内样式
    想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现:
// 注意这里的两个括号,第一个表示我们在要JSX里插入JS了,第二个是对象的括号
<p style={{color:'red', fontSize: '14px'}}>Hello world</p>

行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,例如render 函数里、组件原型上、外链js文件中

  • 使用class
    React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
    其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是, class 需要写成className (因为毕竟是在写类js代码,会收到js规则的现在,而class 是关键字)
<p className="hello">Hello world</p>

注意:
class ==> className , for ==> htmlFor(label)

5. 事件处理

采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写 onclick , React里的事件是驼峰 onClick ,React的事件并不是原生事件,而是合成事件。

import React, { Component } from 'react'
export default class App extends Component {
    a = 100
    render() {
        return (
            <div>
                <input />
                <button  onClick={ ()=>{
                    console.log("click1","如果处理逻辑过多, 不推荐这种写法",this.a)
                } }>add1</button>
                <button  onClick={ this.handleClick2.bind(this)  }>add2-不推荐这种写法</button>
                <button  onClick={ this.handleClick3  }>add3-推荐</button>
                <button  onClick={ ()=>this.handleClick4() }>add4-比较推荐-传参</button>
            </div>
        )
    }
    handleClick2(){
        console.log("click2",this.a)
    }
    handleClick3 = (evt)=>{
        console.log("click3",this.a,evt.target)
    }
    handleClick4(){
        console.log("click4",this.a)
    }
}
/*
React并不会真正的绑定事件到每一个具体《》的元素上,而是采用事件代理的模式:
*/
/*
  call, 改变this, 自动执行函数
  apply,改变this, 自动执行函数
  bind, 改变this, 不会自动执行函数,手动加括号执行函数
*/
var obj1 = {
    name:"obj1",
    getName(){
        console.log(this.name)
    }
}
var obj2 = {
    name:"obj2",
    getName(){
        console.log(this.name)
    }
}
obj1.getName.bind(obj2)()
// obj2.getName()

直接在render里写行内的箭头函数(不推荐)在组件内使用箭头函数定义一个方法(推荐)
直接在组件内定义一个非箭头函数的方法,然后在render里直接使用 onClick =
{this.handleClick.bind(this)} (不推荐)
直接在组件内定义一个非箭头函数的方法,然后在constructor里bind(this)(推荐)

Event对象和普通浏览器一样,事件hander会被自动传入一个event,event 对象,这个对象和普通的浏览器 对象所包含的方法和属性都基本一致。不同的是 React中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation 、 event.preventDefault 这种常用的方法

6. Ref的应用

6.1 标签Ref

给标签设置ref=“username” 通过这个获取this.refs.username , ref可以获取到应用的真实dom

6.2 组件ref

给组件设置ref=“username” 通过这个获取this.refs.username ,ref可以获取到组件对象

import React, { Component } from 'react'
export default class App extends Component {
    a = 100
    myref = React.createRef()
    render() {
        return (
            <div>
                {/* <input ref="mytext"/> */}
                <input ref={this.myref}/>
                <button  onClick={ ()=>{
                    // console.log("click1",this.refs.mytext.value)
                    console.log("click",this.myref.current.value)
                } }>add1</button>
                <button onClick={this.handleClick2}>add2</button>
            </div>
        )
    }
    handleClick2 = ()=>{
        console.log("click2",this.myref.current.value)
    }
}

6.3 新写法

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

访问this.myRef.current

五、组件的数据挂载方式

1. 状态(state)

状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)

1.1 定义state

this.state 是纯js对象,在vue中,data属性是利用Object.defineProperty处理过的,更改data的数据的时候会触发数据的 getter 和setter ,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法 setState。

  1. 第一种方式
    state = {
        mytext:"收藏",
        myShow:true
    }
  1. 第二种方式
    constructor(){
        super()
        this.state = {
            mytext:"收藏",
            myShow:true,
        }
    }

例:

import React, { Component } from 'react'
export default class App extends Component {
    a = 1
   // state = {
     //   mytext:"收藏",
       // myShow:true
    //}
    constructor(){
        super()
        this.state = {
            mytext:"收藏",
            myShow:true,
            myname:"kerwin"
        }
    }
    render() {
        // var text="收藏"
        return (
            <div>
                <h1>欢迎来到react开发-{this.state.myname}</h1>

                <button onClick={()=>{
                //    this.state.mytext = "取消" //不用直接修改状态
                    this.setState({
                        // mytext:"取消收藏"
                        myShow:!this.state.myShow,
                        myname:"xiaoming"
                    }) // 间接修改状态

                    if(this.state.myShow){
                        console.log("收藏的逻辑")
                    }else{
                        console.log("取消收藏的逻辑")
                    }
                }}>{this.state.myShow?'收藏':"取消收藏"}</button>
            </div>
        )
    }
}

1.2 更新状态(setState)

                   this.setState({
                        // mytext:"取消收藏"
                        myShow:!this.state.myShow,
                        myname:"xiaoming"
                    }) // 间接修改状态

setState 有两个参数

  1. 第一个参数可以是对象,也可以是方法return一个对象,我们把这个参数叫做 updater
参数是对象
this.setState({
isLiked: !this.state.isLiked
})
参数是方法
this.setState((prevState, props) => { return {
isLiked: !prevState.isLiked
}
})

注意的是这个方法接收两个参数,第一个是上一次的state, 第二个是props
2. 因为setState是异步的,相当于不是实时更新数据,所以想要获取最新的state 就需要的第二个参数,是一个可选择的回调参数

this.setState((prevState, props) => { return {
isLiked: !prevState.isLiked
}
}, () => {
console.log('回调里的 ',this.state.isLiked)
})
console.log('setState外部的 ',this.state.isLiked)

2. key值

循环渲染 为了列表的复用和重排, 设置key值, 提高性能; 理想key ,item.id 不涉及到列表的增加删除 ,重排,
设置成索引没有问题。

//状态
 state = {
        list:[{id:1,text:"1111"},
        {id:2,text:"2222"},
        {id:3,text:"3333"}]
    }
 // 使用map渲染
 <div><ul>{
this.state.list.map( 
	(item,index)=>
	<li key={index}>{item.text}--{index}</li>
 	)
}</ul></div>

原生js写法

/*
 原生js - map
*/
var list= ["aa","bb","cc"]
var newlist = list.map(item=>`<li>${item}</li>`)
console.log(newlist.join(""))

3. setstate的同步异步问题

  • setState处在同步的逻辑中, 异步更新状态,更新真实dom
  • setState处在异步的逻辑中, 同步更新状态,同步更新真实dom,
  • setState 接受第二个参数, 第二个参数式回调函数, 状态和dom更新完后就会被触发。
import React, { Component } from 'react'
export default class App extends Component {
    state  ={    count:1    }
    render() {
        return (
            <div>
               {this.state.count}
               <button onClick={this.handleAdd1}>add1</button> 
               <button onClick={this.handleAdd2}>add2</button> 
            </div>
        )
    }
    handleAdd1 = ()=>{
        this.setState({
            count:this.state.count+1
        },()=>{
            console.log(this.state.count)
        })
        this.setState({
            count:this.state.count+1
        },()=>{
            console.log(this.state.count)
        })
        this.setState({
            count:this.state.count+1
        },()=>{
            console.log(this.state.count)
            // 状态 和真实dom已经更新完了,
        })
    }
    handleAdd2 = ()=>{
        setTimeout(()=>{
            this.setState({
                count:this.state.count+1
            })
            console.log(this.state.count)
            this.setState({
                count:this.state.count+1
            })
            console.log(this.state.count)
            this.setState({
                count:this.state.count+1
            })
            console.log(this.state.count)
        },0)
    }
}

4. BetterScroll介绍

链接: BetterScroll

BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的 iscroll (opens new
window)的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature
以及做了一些性能优化。 BetterScroll 是使用纯 JavaScript 实现的,这意味着它是无依赖的。

引入依赖

import BetterScroll from 'better-scroll'

使用方法
直接绑定组件dom节点的className

import React, { Component } from 'react'
import BetterScroll from 'better-scroll'
export default class App extends Component {
    state = {list:[]}
    render() {
        return (
            <div>
                <button onClick={()=>this.getData()}>click</button>
                <div className="kerwinwrapper" style={{height:'200px',background:'yellow',overflow:'hidden'}}>
                    <ul className="kerwincontent">
                       {
                           this.state.list.map(item=>
                           <li key={item}>{item}</li>
                            )
                       }
                    </ul>
                </div>
            </div>
        )
    }
    getData(){
        var list =[1,2,3,4,5,6,7,8,9,0,11,12,13,14,15]
        // this.setState({
        //     list:list
        // },()=>{
        //     console.log(this.state.list)
        //     console.log(document.querySelectorAll("li"))
        //     new BetterScroll(".kerwinwrapper")
        // }
        证明setState 为异步调用
        setTimeout(()=>{
            this.setState({list:list })
            console.log(this.state.list)
            console.log(document.querySelectorAll("li"))
            new BetterScroll(".kerwinwrapper")
        },0)
    }
}

5. 属性(props)

props是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的props属性是描述性质、特点的,组件自己不能随意更改。

之前的组件代码里面有props 的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的
属性当中,所有的属性都会作为组件参数来接收props : 对象的键值。通过箭头函数创建的组件,需要通过函数的

  1. 在组件上通过key=value 写属性,通过this.props获取属性,这样组件的可复用性提高了。
  2. 注意在传参数时候,如果写成isShow=“true” 那么这是一个字符串如果写成isShow={true} 这个是布尔值
  3. {…对象} 展开赋值
  4. 默认属性值
  5. prop-types 属性验证
state = {//只能内部自己用的,外面无法改变。}
//父组件定义
var obj = {title:"测试",leftshow:false }
<Navbar title={obj.title} leftshow={obj.leftshow} />
<Navbar {...obj} />

//子组件接受
//属性是父组件传来的,this.props
 let {title,leftshow}  = this.props

// 类属性
import propTypes from 'prop-types' 引入校验方法
    static propTypes = {
        title:propTypes .string, //做校验
        leftshow:propTypes .bool //做校验
    }

//默认值
    static defaultProps = {
        leftshow:true
    }

6. 属性状态的区别

相似点:

  • 都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)

不同点:

  1. 属性能从父组件获取,状态不能
  2. 属性可以由父组件修改,状态不能
  3. 属性能在内部设置默认值,状态也可以,设置方式不一样
  4. 属性不在组件内部修改,状态要在组件内部修改
  5. 属性能设置子组件初始值,状态不可以
  6. 属性可以修改子组件的值,状态不可以

state 的主要作用是用于组件保存、控制、修改自己的可变状态。 state
在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。
state 中状态可以通过 this.setState 方法进行更新, setState 会导致组件的重新渲染。 props
的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。非外部组件主动传入新的
props ,否则组件的 props 永远保持不变。

没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful
component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复性。

通俗的讲,状态就是自己内在的心情,可以自己控制调节,而属性就像是父母给的外貌与性别,不能随意更改。

父子之间信息传递,属于单向数据流
父——》子,子想更改,子——》父告诉父想更改,再父——》子

import React, { Component } from 'react'
class Child extends Component{
    render(){
        return <div>
            child-{this.props.text}
			<button onClick={()=>{
                this.props.text = "333333333333333333"
            }}>click-</button>
        </div>
    }
}
export default class App extends Component {
    state = {text:"111111111"}
    render() {
        return (
            <div>
                <button onClick={()=>{
                    this.setState({
                        text:"2222222"
                    })
                }}>click-</button>
                <Child text = {this.state.text}/>
            </div>
        )
    }
}

7. 渲染数据

  • 条件渲染 :react代码大量使用条件渲染,三元表达式,或者&&
{condition ? '渲染列表的代码' : '空空如也'}
  • 列表渲染
// 数据
const people = [{ id: 1,name: 'Leo', age: 35}, {id: 2,name: 'XiaoMing', age: 16}]
// 渲染列表
{
people.map(person => { return 
		(
				<dl key={person.id}>
				<dt>{person.name}</dt>
				<dd>age: {person.age}</dd>
				</dl>
		)
	})
}

React的高效依赖于所谓的 Virtual-DOM,尽量不碰 DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下 DOM 位置就行了,但是React并不知道 其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM ),这样会大 大增加 DOM 操作。但如果给每个元素加上唯一的标识,React 就可以知道这两个元素只是交换了位 置,这个标识就是 key ,这个 key 必须是每个元素唯一的标识。

  • dangerouslySetInnerHTML

对于富文本创建的内容,后台拿到的数据是这样的:

content = "<p>React.js是一个构建UI 的库</p>"

处于安全的原因,React当中所有表达式的内容会被转义,如果直接输入,标签会被当成文本。这时候就需要使用dangerouslySetHTML 属性,它允许我们动态设置innerHTML

import React, { Component } from 'react' 
import ReactDOM from 'react-dom'
class App extends Component {
	constructor() {
	super() this.state = {content : "<p>React.js是一个构建UI 的库</p>"}
	}
	render () { 
		return (
			<div
			// 注意这里是两个下下划线   html 
			dangerouslySetInnerHTML={{ __html: this.state.content}}
			/>
			)
		}
	}
ReactDOM.render(
<App/>, 
document.getElementById('root')
)

六、表单中的受控组件与非受控组件

1. 非受控组件

React要编写一个非受控组件,可以 使用 ref 来从 DOM 节点中获取表单数据,就是非受控组件。

例如,下面的代码使用非受控组件接受一个表单的值:

import React, { Component } from 'react'
export default class App extends Component {
    myusername = React.createRef()  //ref绑定到input组件上
    render() {
        return (
            <div>
                <h1>登录页</h1>
                <input type="text" ref={this.myusername} defaultValue="kerwin"/>
                
				<button onClick={()=>{
                    console.log(this.myusername.current.value)
                }}>登录</button>
                <button onClick={()=>{
                    this.myusername.current.value = ""
                }}>重置</button>
                {/* <Child myvalue={this.myusername.current.value}/> */}
            </div>
        )
    }
}

?为什么input不使用value绑定数据,这里可以把input当成一个React的封装组件,value值相当于子组件的属性,属性由子修改,所以只能更新一次。

因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。

默认值
在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个defaultValue 属性,而不是 value 。

render() {
return (
	<form onSubmit={this.handleSubmit}>
		<label>
			Name:
			<input
				defaultValue="Bob"
				type="text"
				ref={this.input} />
		</label>
		<input type="submit" value="Submit" />
	</form>
	);
}
同样, <input type="checkbox"><input type="radio"> 支持 defaultChecked , <select><textarea> 支持 defaultValue 。

2. 受控组件

  1. 表单元素依赖于状态,表单元素需要默认值实时映射到状态的时候,就是受控组件,这个和双向绑定相似.
  2. 受控组件,表单元素的修改会实时映射到状态值上,此时就可以对输入的内容进行校验.
  3. 受控组件只有继承React.Component才会有状态.
  4. 受控组件必须要在表单上使用onChange事件来绑定对应的事件.
import React, { Component } from 'react'
export default class App extends Component {
    // myusername = React.createRef()
    // 这样的写法也是声明在实例上的对象
	state = {username:"name"}// 给组件状态设置默认值,在实时修改时进行校验
    render() {
        return (
            <div>
                <h1>登录页</h1>
               // evt为原生的事件绑定对象
                <input type="text" value={this.state.username} onChange={(evt)=>{
                    console.log("onChange",evt.target.value)
						 this.setState({
                        username:evt.target.value // 获取原生对象上的属性并更新
                    })
                }}/>
				 <button onClick={()=>{
                    console.log(this.state.username)
                }}>登录</button>
                <button onClick={()=>{
						this.setState({
                        username:"" 
                        })
                }}>重置</button>
                {/* <Child myvalue={this.state.username}/> */}
            </div>
        )
    }
}

受控组件更新state的流程:

  1. 可以通过初始state中设置表单的默认值
  2. 每当表单的值发生变化时,调用onChange事件处理器
  3. 事件处理器通过事件对象event拿到改变后的状态,并更新组件的state
  4. 通过setState方法更新state,就会触发视图的重新渲染,完成表单组件的更新

于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value ,这使得 React 的 state
成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。

对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI
元素,或者通过其他事件处理函数重置,但这意味着你需要编写更多的代码。

注意: 另一种说法(广义范围的说法),React组件的数据渲染是否被调用者传递的 props 完全控制,控制则为受控组件,否则非受控组件。

3. 受控组件与非受控组件区别

  • 受控组件:(推荐)
  1. 在React中默认可以完成从state到表单value的动态绑定。
  2. 给input提供onChange事件,一旦检测到文本框内容有变化,马上执行onChange事件获取表单的内容。
  • 非受控组件:
  1. 操作DOM获取到你要的数据,ref属性接受一个回调函数,返回一个element节点 , 通过节点获取到数据 ref={(element)=>this.addressElement = element }
  2. 可以在构造函数里面定义一个变量,通过变量来创建组件引用,就可以获取到这个节点

七、组件通信

1. 父子组件通信

数据通信是单向的,数据必须是由一方到另一方。

1.1 传递数据与传递方法

  1. 父传子:属性
    在 React 中,父组件可以向子组件通过传 props 的方式,向子组件进行通讯。
  2. 子传父:回调函数(callback)

代码示例:

import React, { Component } from 'react'
class Navbar extends Component{
    render(){
        return <div style={{background:"red"}}>
            <button onClick={()=>{
                   console.log("子通知父, 让父的isSHow 取反。",this.props.event) 

                   this.props.event() //调用父组件传来啊的回调函数
            }}>click</button>
            <span>navbar</span>
        </div>
    }
}
class Sidebar extends Component{
    render(){
        return <div style={{background:"yellow",width:"200px"}}> 
            <ul>
                <li>11111</li>
                <li>11111</li>
                <li>11111</li>
            </ul>
        </div>
    }
}
export default class App extends Component {
    state = {
        isShow:false
    }
	handleEvent = ()=>{
        this.setState({
            isShow:!this.state.isShow
        })
        // console.log("父组件定义的event事件")
    }
    render() {
        return (
            <div>
                <Navbar event={this.handleEvent}/>
                {/* <button onClick={()=>{
                    this.setState({
                        isShow:!this.state.isShow
                    })
                }}>click</button> */}
                {this.state.isShow && <Sidebar/>}
            </div>
        )
    }
}

父子通信版表单域组件

import React, { Component } from 'react'
class Field extends Component{
    render(){
        return <div style={{background:"yellow"}}>
            <label>{this.props.label}</label>
            <input type={this.props.type} onChange={(evt)=>{
                // console.log(evt.target.value)

                this.props.onChangeEvent(evt.target.value)
            }} value={this.props.value}/>
        </div>
    }
}

export default class App extends Component {
 	state = {
        username:localStorage.getItem("username"),
        password:""
    }
	render() {
        return (
            <div>
                <h1>登录页面</h1>

                <Field label="用户名" type="text" onChangeEvent={(value)=>{
                    // console.log(value)

                    this.setState({
                        username:value
                    })
                }} value={this.state.username}/>
                <Field label="密码" type="password" onChangeEvent={(value)=>{
                    
                    this.setState({
                        password:value
                    })
                }} value={this.state.password}/>

                <button onClick={()=>{
                    console.log(this.state.username,this.state.password,'发送后端验证')
                }}>登录</button>
                <button onClick={()=>{
                    this.setState({
                        username:"",
                        password:""
                    })
                }}>取消</button>
            </div>
        )
    }
}

2.1 ref标记

import React, { Component } from 'react'


class Field extends Component{
    state =  {
        value:""
    }
	clear(){
        this.setState({
            value:""
        })
    }
	setValue(value){
        this.setState({
            value:value
        })
    }
	render(){
        return <div style={{background:"yellow"}}>
            <label>{this.props.label}</label>
            <input type={this.props.type} onChange={(evt)=>{
                this.setState({
                    value:evt.target.value
                })
            }} value={this.state.value}/>
        </div>
    }
}
export default class App extends Component {
    username = React.createRef()
    password = React.createRef()
    render() {
        return (
            <div>
                <h1>登录页面</h1>
				<Field label="用户名" type="text" ref={this.username}/>
                <Field label="密码" type="password" ref={this.password}/>

                <button onClick={()=>{
                    console.log(this.username.current.state.value,
                        this.password.current.state.value
                        )
                }}>登录</button>
                <button onClick={()=>{
                    this.username.current.clear()
                    this.password.current.clear()
                }}>取消</button>
            </div>
        )
    }
}

父组件拿到子组件的引用,从而调用子组件的方法。

2. 非父子组件通信

2.1 状态提升(中间人模式)

React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近的父组件上.在父组件上改变这个状态然后通过props分发给子组件。

import React, { Component } from 'react'

export default class APP extends Component {
  state = {
    title: ''
  }
  render() {
    return (
      <div>
        <One childTitle={this.state.title} onEvent={(e) => {
          this.setState({
            title: e
          })
        }} />
        <Two twoTitle={this.state.title} />
      </div>
    )
  }
}
//One组件向Two组件传信息
class One extends Component {
  state = {
    oneTitle: "rain"
  }
  render() {
    return (
      <div>
        <button onClick={() => {
          this.props.onEvent(this.state.oneTitle)
        }}>点击</button>
      </div>
    )
  }
}


class Two extends Component {
  render() {
    return (
      <div style={{ backgroundColor: "yellow", width: '100px', height: "100px" }}>{this.props.twoTitle}</div>
    )
  }
}

2.2 发布者订阅者模式

subsrcibe()存入函数到list数组等待运行,调用publish()函数运行list数组中全部函数。
下面的代码中,仅在Two组件的constructor()函数中运行了一次subsrcibe()函数,所以list数组中仅有一个函数,每次运行publish()函数时,运行(text) => { this.setState({ info: text }) }

import React, { Component } from 'react'

// 调度中心
var obj = {
  list: [],
  // 每次调用此函数,存入一个函数到list
  subsrcibe(callback) { //生产者
    // console.log(callback)
    this.list.push(callback)
  },
  // 每次调用此函数,运行list数组里的全部函数
  publish(text) { //消费者
    console.log(text)
    // 遍历所有的list,将回调函数执行
    this.list.forEach(callback => {
      callback && callback(text)
    })
  }
}

export default class APP extends Component {
  state = {
    list: [1, 2, 3, 4, 5]
  }
  render() {
    return (
      <div>
        {
          this.state.list.map(item => <One key={item} msg={item} ></One>)
        }
        <Two />
      </div>
    )
  }
}


class One extends Component {
  render() {
    return (
      <div style={{ backgroundColor: "green", height: "20px", width: "20px" }} onClick={() => {
        obj.publish(this.props.msg)
      }
      }>
        <div>{this.props.msg}</div>
      </div >
    )
  }
}

class Two extends Component {
  constructor() {
    super()
    obj.subsrcibe((text) => {
      this.setState({
        info: text
      })
    })
  }
  state = {
    info: ''
  }
  render() {
    return (
      <div style={{ backgroundColor: 'yellow', width: '100px', height: '100px' }}>
        {this.state.info}
      </div>
    )
  }
}

2.3 context上下文状态树传参模式

  1. 先定义全局context对象
  2. 根组件引入GlobalContext,并使用GlobalContext.Provider(生产者)
  3. 任意组件引入GlobalContext并调用Context,使用GlobalContext.Consumer(消费者)
import React, { Component } from 'react'

const GlobalContext = React.createContext() //创建context对象

export default class Tewlfth extends Component {
  state = {
    user: 'rain',
    sex: true
  }
  render() {
    return (
      <GlobalContext.Provider value={{ //生产者
        user: this.state.user,
        sex: this.state.sex,
        changeUser: (value) => {
          this.setState({
            sex: !value
          })
        }
      }}>
        <div>
          <One />
          <Two />
        </div>
      </GlobalContext.Provider>
    )
  }
}
class One extends Component {
  render() {
    return (
      <GlobalContext.Consumer> //消费者
        {
          (value) => {
            return (
              <div style={{ backgroundColor: "green", height: "100px", width: "100px" }} >
                <div>{value.user}</div>
                <button onClick={() => {
                  value.changeUser(value.sex)
                }}>点击</button>
              </div >
            )
          }
        }
      </GlobalContext.Consumer >
    )
  }
}

class Two extends Component {
  render() {
    return (
      <GlobalContext.Consumer> //消费者
        {
          (value) => {
            return (
              <div style={{ backgroundColor: "yellow", width: "100px", height: '100px' }}>{value.sex ? "男" : "女"}</div>
            )
          }
        }
      </GlobalContext.Consumer>
    )
  }
}

注意:GlobalContext.Consumer内必须是回调函数,通过context方法改变根组件状态

context优缺点

  • 优点:跨组件访问数据
  • 缺点:react组件树种某个上级组件shouldComponetUpdate 返回false,当context更新时,不会引起下级组件更新

2.4 React插槽(Portals)

Portals 提供了一种很好的方法,将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点。
jsx中的所有内容都会通过children prop传递到父组件中,使用react组合的方式可以实现类似于Vue插槽的功能;
示例:

import React, { Component } from 'react'
class Child extends Component{
    render(){
        return <div>
            child

            {/* 插槽 vue slot,具名插槽 */}

            {this.props.children[2]}   //父组件中有多个dom节点时,会被渲染成数组
            {this.props.children[1]}
            {this.props.children[0]}
        </div>
    }
}
class Swiper extends Component{
    render(){
        return <div>
            {this.props.children}
        </div>
    }
}
export default class App extends Component {
    render() {
        return (
            <div>
                <Swiper>
                    <div>111111</div>
                    <div>222222</div>
                    <div>333333</div>
                </Swiper>
                <Swiper>
                    {/* <div><img/>111111</div>
                    <div><img/>222222</div>
                    <div><img/>333333</div> */}
                </Swiper>
                <Swiper>
                    {/* <img/>
                    <img/>
                    <img/> */}
                </Swiper>
                <Child>
                    <div>11111111</div>
                    <div>22222222</div>
                    <div>33333333</div>
                    {
                        // children
                    }
                </Child>
            </div>
        )
    }
}

优点:

  1. 为了复用
  2. 一定程度减少父子通信。

八、生命周期

组件的生命周期可分成三个状态:

  1. Mounting(挂载):已插入真实 DOM
  2. Updating(更新):正在被重新渲染
  3. Unmounting(卸载):已移出真实 DOM

简单生命周期
在这里插入图片描述
包含不常用生命周期
在这里插入图片描述

1. 挂载(初始化)

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  1. constructor(): 在 React 组件挂载之前,会调用它的构造函数。
  2. getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  3. render(): render() 方法是 class 组件中唯一必须实现的方法。
  4. componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

import React, { Component } from 'react'
export default class App extends Component {
    state = {
        myname:"name"
    }
    UNSAFE_componentWillMount(){ //新版本已经被遗弃,加上UNSAFE表示不安全的,谨慎使用
        console.log("第一次will mount",this.state.myname,document.getElementById("myname"))
        // 第一次初始化的 最后一次修改状态机会
        this.setState({
            myname:"name"
        })
        //初始化数据的作用。
    }
    componentDidMount(){
        console.log("第一次did mount",document.getElementById("myname"))
        // 数据请求axios
        // 订阅函数调用
        // setInterval
        // 基于创建的完的dom进行 初始化,,,,,,BetterScroll
    }
    render() {
        console.log("render")
        return (
            <div>
                <span id="myname">{this.state.myname}</span>
            </div>
        )
    }
}

2. 更新

每当组件的 state 或 props 发生变化时,组件就会更新。

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  1. getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据shouldComponentUpdate() 的返回值,判断 React组件的输出是否受当前 state 或 props 更改的影响。
  2. shouldComponentUpdate():当 props 或 state发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  3. render(): render() 方法是 class 组件中唯一必须实现的方法。
  4. getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  5. componentDidUpdate(): 在更新后会被立即调用。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

例1:

import React, { Component } from 'react'
export default class App extends Component {
    state  = {
        myname:"name"
    }
    render() {
        console.log("render")
        return (
            <div>
                <button onClick={()=>{
                    // this.state.myname = "xiaoming"
                    this.setState({
                        myname:"xiaoming"
                    })
                }}>click</button>
                 {this.state.myname}
            </div>
        )
    }
    // scu 性能优化函数
    shouldComponentUpdate(nextProps,nextState){
        // return true; //应该更新
        //return false;; //阻止更新
        // this.state  老的状态
        // nextState   新的状态
        if(JSON.stringify(this.state)!== JSON.stringify(nextState)){
            return true
        }
		return false //阻止之后不进行之后的渲染
    }
	UNSAFE_componentWillUpdate(){ //将要更新前的数据操作
        console.log("UNSAFE_componentWillUpdate")
    }
    componentDidUpdate(prevProps,prevState){//已经更新后的数据操作  // 更新后, 想要获取dom节点, 更新
    //prevState 老状态
        console.log("componentDidUpdate")
         //new BetterScroll("#warpper") 比如BetterScroll操作
    }
}

例2:componentWillReceiveProps,子组件中使用

import React, { Component } from 'react'
class Child extends Component{
	state  ={
        title:""
    }
    render(){
    return <div>child-{this.state.title}</div>
    }
	componentWillReceiveProps(nextProps){
        console.log("componentWillReceiveProps",nextProps)
        // 最先获得父组件传来的属性, 可以利用属性进行ajax或者逻辑处理。
        // 把属性转化成孩子自己的状态。
        this.setState({
            title:nextProps.text+"name"
        })
    }
}
export default class App extends Component {
    state = {
        text:"11111111111"
    }
    render() {
        return (
            <div>
                {
                    this.state.text
                }
                <button onClick={()=>{
                    this.setState({
                        text:"222222222222"
                    })
                }}>click</button>
                <Child text={this.state.text}/>
            </div>
        )
    }
}

3. 卸载(销毁)

当组件从 DOM 中移除时会调用如下方法:

  1. componentWillUnmount(): 在组件卸载及销毁之前直接调用。
import React, { Component } from 'react'
export default class App extends Component {
    state = {
        isCreated:true
    }
    render() {
        return (
            <div>
                <button onClick={()=>{
                    this.setState({
                        isCreated:!this.state.isCreated
                    })
                }}>click</button>
                {/* {this.state.isCreated?<Child/>:""} */}
                {this.state.isCreated && <Child/>}
            </div>
        )
    }
}
class Child extends Component{
    render(){
        return <div>
            child
        </div>
    }
	componentDidMount() {
        window.onresize = ()=>{
            console.log("resize")
        }
		this.timer = setInterval(()=>{
            console.log("111")
        },1000)
    }
    componentWillUnmount(){ //不进行销毁,执行函数会一直进行下去,必须进行销毁操作
        console.log("componentWillUnmount")
         window.onresize = null
         clearInterval(this.timer) 
    }
}

4. 新旧生命周期差异问题

4.1 老生命周期问题

  1. componentWillMount ,在ssr中 这个方法将会被多次调用,所以会重复触发多遍,同时在这里如果绑定事件,将无法解绑,导致内存泄漏 , 变得不够安全高效逐步废弃。
  2. componentWillReceiveProps 外部组件多次频繁更新传入多次不同的 props,会导致不必要的异步请求
  3. componetWillupdate, 更新前记录 DOM 状态,可能会做一些处理,与componentDidUpdate相隔时间如果过长, 会导致 状态不太信

4.2 新生命周期的替代

  1. getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子) ,返回一个对象作为新的state,返回null则说明不需要在这里更新 state
//老的生命周期的写法 
componentDidMount() {
	if(this.props.value!==undefined){ this.setState({
			current:this.props.value
		})
	}
}
componentWillReceiveProps(nextProps){ 
	if(nextProps.value !==undefined){
			this.setState({ current:nextProps.value
		})
	}
}
// 新的生命周期写法
static getDerivedStateFromProps(nextProps) { 
	if(nextProps.value !==undefined){
		return {
			current:nextProps.value
		}
	}
	return null
}
  1. getSnapshotBeforeUpdate 取代了 componetWillUpdate ,触发时间为update发生的时候,在render之后dom渲染之前返回一个值,作为componentDidUpdate的第三个参数。
//新的数据不断插入数据前面, 导致我正在看的数据向下走,如何保持可视区依旧是我之前看的数据呢?

getSnapshotBeforeUpdate(){ //保留上一次的状态
	return this.refs.wrapper.scrollHeight
}
componentDidUpdate(prevProps, prevState,preHeight) {
//if(preHeight===200)return ;
	this.refs.wrapper.scrollTop += this.refs.wrapper.scrollHeight-preHeight
}
<div style={{height:"200px",overflow: "auto"}}} ref="wrapper">
	<ul>
	.........
	</ul>
</div>

新生命周期例子1:

import React, { Component } from 'react'
export default class App extends Component {
    state  = {
        myname:"name",
        myage:100
    }
    // componentWillMount 初始化
    static getDerivedStateFromProps(nextProps,nextState){ //属于静态方法 是类方法
        // console.log(this)
        console.log("getDrivedStateFromProps",nextState)
        return {
            myname:nextState.myname.substring(0,1).toUpperCase()+nextState.myname.substring(1)
        }
    }
	render() {
        return (
            <div>
                <button onClick={()=>{
                    this.setState({
                        myname:"xiaoming"
                    })
                }}>click</button>
                app - {this.state.myname}-{this.state.myage}
            </div>
        )
    }
}

新生命周期例子2:

import React, { Component } from 'react'
export default class App extends Component {
    state = {
        mytext:"111111"
    }
    render() {
        console.log("render")
        return (
            <div>
                app
                <button onClick={()=>{
                    this.setState({
                        mytext:"222222"
                    })
                }}>click</button>
                {this.state.mytext}
            </div>
        )
    }
	 // componentWillUpdate(){
    //  console.log("componentWillUpdate")
    // }

    componentDidUpdate(prevProps,prevState,value){
        console.log("componentDidUpdate",value)
    }

    getSnapshotBeforeUpdate(){
        console.log("getSnapshotBeforeUpdate")
        return 100;
    }
}

5. react中性能优化的方案

5.1 shouldComponentUpdate

控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下, 需要进行优化。

5.2 PureComponent

PureComponent会帮你 比较新props 跟 旧的props, 新的state和老的state(值相等,或者对象含有相同的属性、且属性值相等 ),决定shouldcomponentUpdate 返回true 或者 false, 从而决定要不要呼叫 render function。

注意
如果你的 state 或 props 『永远都会变』,那 PureComponent 并不会比较快,因为shallowEqual 也需要花时间。

例:

import React, { PureComponent } from 'react'
export default class App extends PureComponent {
    state  = {
        myname:"kerwin"
    }
    render() {
        console.log("render")
        return (
            <div>
                <button onClick={()=>{
                    // this.state.myname = "xiaoming"
                    this.setState({
                        myname:"xiaoming"
                    })
                }}>click</button>

                {this.state.myname}
            </div>
        )
    }
    // scu 性能优化函数
    
    // shouldComponentUpdate(nextProps,nextState){
    //     // return true; //应该更新
    //     //return false;; //阻止更新
    //     // this.state  老的状态
    //     // nextState   新的状态
    //     if(JSON.stringify(this.state)!== JSON.stringify(nextState)){
    //         return true
    //     }

    //     return false
    // }

    UNSAFE_componentWillUpdate(){
        console.log("UNSAFE_componentWillUpdate")
    }
    componentDidUpdate(){
        console.log("componentDidUpdate")
    }
}

6. react 生命周期与 vue 生命周期比较

九、React Hooks(16.8之后引入)

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook 本质上就是一个函数,它简洁了组件,有自己的状态管理,生命周期管理,状态共享。

使用hooks的理由

  1. 高阶组件为了复用,导致代码层级复杂
  2. 解决类组件的生命周期的复杂
  3. 写成functional组件,无状态组件 ,因为需要状态,又改成了class,成本高
  4. 高内聚,低耦合

什么时候使用 Hook ?

  1. 组件之间状态复用, 例如:使用 useContext 可以很好的解决状态复用问题,或者自定义 Hook来定制符合自己业务场景遇到的状态管理。

  2. 在函数组件中 生命周期的使用,更好的设计封装组件。在函数组件中是不能直接使用生命周期的,通过 Hook 很好的解决了此问题。

  3. 函数组件与 class 组件的差异,还要区分两种组件的使用场景。使用 Hook 完全不用去想这些,它可以使用更多 React新特性。

什么时候使用 Hook ?

  1. 在函数组件顶层调用
  2. 在 函数中使用 / 自定义 Hook 中使用

React 内置的 Hook

  1. useState 状态管理

  2. useEffect 生命周期管理

  3. useContext 共享状态数据

  4. useMemo 缓存值

  5. useRef 获取 Dom 操作

  6. useCallback 缓存函数

  7. useReducer redux 相似

  8. useImperativeHandle 子组件暴露值 / 方法

  9. useLayoutEffect 完成副作用操作,会阻塞浏览器绘制

1. useState(保存组件状态)

在 class 组件中,我们获取 state 是 通过 this.state 来获取的。 而在函数组件中,是没有 this 的,
我们可以使用 Hook 提供的 useState 来管理和维护 state

1.1 useState 定义 / 使用

const [state, setState] = useState(initialState)

 1. setState 为更新 satate 方法
 2. useState(initialState) initialState 为初始值

例子:

import {useState} from 'react';
export default () => {
    const [data, setData] = useState('小明')
    return (
        <div>
            <h1>{data}</h1>
            {/* 更新 state */}
            <button onClick={()=>{setData('小李')}}></button>
        </div>
    )
}

2. useEffect(处理副作用)

2.1 useEffect定义

useEffect 可以看作是 函数式 组件 的 生命周期管理。 因为在 函数式组件中无法直接使用生命周期,就必须托管 Hook来进行管理使用了。

useEffect 可以使用的 3 个生命周期函数:

  1. componentDidmount
  2. componentDidUpdate
  3. componentWillUnmount

Function Component 不存在生命周期,所以不要把 Class Component 的生命周期概念搬过来试图对号入座。

useEffect(() => {
	//effect return () => {
	//cleanup
	};
}, [依赖的状态;空数组,表示不依赖])

不要对 Dependencies 撒谎, 如果你明明使用了某个变量,却没有申明在依赖中,你等于向 React 撒了谎,后果就是,当依赖的变量改变时,useEffect 也不会再次执行, eslint会报警告

2.2 无需清除 Effect 使用

React 更新 DOM 之后运行一些额外的代码」
那么它就是在生命周期的 compoentDidmount 和 componentUpdate 中执行即可

 useEffect(() => {
        //默认会执行  
        // 这块相当于 class 组件 生命周期的
        //compoentDidmount    compoentDidUpdate
    }, [])

2.3 清除 Effect 使用

当组件进行卸载时,需要执行某些事件处理时,就需要用到 class 组件生命周期的 componentUnmount .
在 useEffect 中很方便使用,在内部返回一个方法即可,在方法中写相应业务逻辑
useEffect返回返回函数解释
这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

 useEffect(()=>{
        return () => {
            console.log('组件卸载时执行')
        }
    })

2.4 监听 state 变化

可以通过控制 监听 state 变化来实现相应的业务逻辑。

useEffect(() => {
        // 监听num,count  状态变化
        // 不监听时为空 [] , 或者不写
    }, [num, count])

例子:

import { useState, useEffect } from 'react';

export default () => {
    const [num, setNum] = useState(0)
    const [count, setCount] = useState(1)

    useEffect(() => {
        //默认会执行  
        // 这块相当于 class 组件 生命周期的 compoentDidmount compoentDidUpdate
        console.log(`num: ${num}`)
        console.log(`count: ${count}`)

        // 组件在卸载时,将会执行 return 中内容
        return () => {
            // 相当于 class 组件生命周期的 componentWillUnMount 
            console.log('测试')
        }
    }, [num])

    return (
        <div>
            <h1>{num}</h1>
            <button onClick={() => { setNum(num + 1) }}> 更新Num</button>
            <hr />
            <h1>{count}</h1>
            <button onClick={() => { setCount(count + 1) }}> 更新Count</button>
        </div>
    )
}

3. useEffect和useLayoutEffect有什么区别?

简单来说就是调用时机不同, useLayoutEffect 和原来 componentDidMount & componentDidUpdate 一致,在 react完成 DOM更新后马上同步调用的代码,会阻塞页面渲染。而 useEffect 是会在整个页面渲染完才会调用的代码。
官方建议优先使用useEffect

However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.

在实际使用时如果想避免页面抖动(在useEffect 里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect 里。在这里做点dom操作,这些 dom修改会和 react 做出的更改一起被一次性渲染到屏幕上,只有一次回流、重绘的代价。

4. useCallback(记忆函数)

防止因为组件重新渲染,导致方法被重新创建 ,起到缓存作用; 只有第二个参数 变化了,才重新声明一次

例子:

import { useState, useCallback} from 'react';
export default () => {
    const  [count, setCount] = useState(0)
    const [num, setNum] = useState(0)
    const newValue = useCallback(()=>{
        console.log(`count 值为${count}`)
        console.log(`num 值为 ${num}`)
        return count+num
    },[count])
    return(
        <div>
            <h1>{count}</h1> 
            <button onClick={()=>{setCount(count+1)}}>count + 1</button>
            <hr/>
            <h1>{num}</h1> 
            <button onClick={()=>{setNum(num+1)}}>Num + 1</button>
            <hr/>
            {/* 调用useCallback 返回的值 */}
            <h2>{newValue()}</h2>
        </div>
    )
}

5. useMemo (记忆组件)

useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。

唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。
所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).

6. useRef(保存引用值)

useRef 返回的是一个可变的 ref 对象,它的属性 current 被初始化为传入的参数(initialValue),「返回的 ref对象在组件的整个生命周期内保持不变」。

作用:

  1. 获取 Dom 操作,例如 获取 input 焦点
  2. 获取子组件的实例 (只有类组件可用)
  3. 在函数组件中的一个全局变量,不会因为重复 render 重复申明

例子:

import {useRef} from 'react';


export default () => {
    const inputRef = useRef({value:0})
    return (
        <div>
            <h1>测试</h1>
            <input type="text" ref={inputRef} />
            <button onClick={()=>{console.log(inputRef.current.value)}}>获取input 值</button>
            <button onClick={()=>{inputRef.current.focus()}}>获取input 焦点</button>
        </div>
    )
}

7. useReducer和useContext(减少组件层级)

7.1 useContext

在使用 Context 时,它通常用在顶级组件 (父组件上),它包裹的内部组件都可以享受到 state 的使用和修改。
通过 Context.Provider 来进行包裹,值通过 value = {} 传递。

子组件如何使用 Context 传递过来的值?
通过 useContext() Hook 可以很方便的拿到对应的值.
例子:

import React, { useState,useEffect,useContext } from 'react'
import axios from 'axios'
import './css/index.css'
const GlobalContext  = React.createContext() //创建context对象
export default function App (){
    const [filmList, setfilmList] = useState([])
    const [info, setinfo] = useState("")

    useEffect(() => {
        axios.get(`/test.json`).then(res=>{
            // console.log(res.data.data.films)
            setfilmList(res.data.data.films)
        })
    }, [])
	return (
        <GlobalContext.Provider value={{
            call:"打电话",
            sms:"短信",
            info:info,
            changeInfo:(value)=>{
                setinfo(value)
            }
        }}>
            <div>
                {/* {this.state.info} */}
                {
                    filmList.map(item=>
                        <FilmItem key={item.filmId} {...item} ></FilmItem>    
                    )
                }
				<FilmDetail ></FilmDetail>
            </div>
        </GlobalContext.Provider>
    )
}

/*受控组件*/

function FilmItem(props){
    let {name, poster,grade,synopsis}  = props
    const value = useContext(GlobalContext)

    // console.log(context)
    return <div className="filmitem" onClick={()=>{
                console.log(synopsis)
                value.changeInfo(synopsis)
            }}>
                <img src={poster} alt={name}/>
                <h4>{name}</h4>
                <div>观众评分:{grade}</div>
            </div> 
}
function FilmDetail(){
    const value = useContext(GlobalContext)
    return <div className="filmdetail">
        detail-{value.info}
    </div>
}

7.2 useReducer

它是 useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

单组件例子:

import React,{useReducer} from 'react'
 //处理函数
 const reducer = (prevState,action)=>{
     console.log("reduercer",prevState,action)
     let newstate = {...prevState}
     switch(action.type){
         case "minus":
            newstate.count--
            return newstate

         case "add":
            newstate.count++
            return newstate
        
         default:
            return prevState
     }
 }
 // 外部的对象
 const intialState = {
     count:0,
    //  list:[]
 } 

 export default function App() {
     const [state, dispatch] = useReducer(reducer,intialState)

     return (
         <div>
             <button onClick={()=>{
                 dispatch({
                     type:"minus"
                 })
             }}>-</button>
             {state.count}
             <button onClick={()=>{
                 dispatch({
                    type:"add"
                })
             }}>+</button>
         </div>
     )
 }

多组件例子:

import React,{useReducer,useContext} from 'react'

const initailState = {
    a:"11111",
    b:"11111"
}

const reducer = (prevState,action)=>{
    let newstate = {...prevState}
    switch(action.type){
        case "change-a":
            newstate.a = action.value
            return newstate
        case "change-b":
            newstate.b = action.value
            return newstate
        default:
            return prevState
    }
    // return prevState
}

const GlobalContext = React.createContext()
export default function App() {
    const [state, dispatch] = useReducer(reducer, initailState)
    
    return (
        <GlobalContext.Provider value={
            {
                state,
                dispatch
            }
        }>
            <div>
                <Child1/>
                <Child2/>
                <Child3/>
            </div>
        </GlobalContext.Provider>
    )
}

function Child1(){
    const {dispatch} = useContext(GlobalContext)
    return <div style={{background:"red"}}>
        <button onClick={()=>{
            dispatch({
                type:"change-a",
                value:"2222222"
            })
        }}>改变a</button>
        <button onClick={()=>{
            dispatch({
                type:"change-b",
                value:"333333"
            })
        }}>改变b</button>
    </div>
}

function Child2(){
    const {state} = useContext(GlobalContext)
    return <div style={{background:"yellow"}}>
        child2-{state.a}
    </div>
}

function Child3(){
    const {state} = useContext(GlobalContext)
    return <div style={{background:"gray"}}>
        child3-{state.b}
    </div>
}

8. 自定义hooks

当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。
必须以“use”开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则

例子:

import React, { useState,useEffect,useMemo } from 'react'
import axios from 'axios'


function useCinemaList(){
    const [cinemaList, setcinemaList] = useState([])


    useEffect(() => {
        axios({
            url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=7406159",
            method:"get",
            headers:{
                'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16395416565231270166529","bc":"110100"}',
                
                'X-Host': 'mall.film-ticket.cinema.list'

            }
        }).then(res=>{
            setcinemaList(res.data.data.cinemas)
        })
    }, [])

    return {
        cinemaList
    }
}

function useFilter(cinemaList,mytext){
    const getCinemaList = useMemo(() => cinemaList.filter(item=>item.name.toUpperCase().includes(mytext.toUpperCase()) || 
    item.address.toUpperCase().includes(mytext.toUpperCase())
    ), [cinemaList,mytext])

    return {
        getCinemaList
    }
}

export default function Cinema(){
    const [mytext, setmytext] = useState("")
    
    const {cinemaList}  = useCinemaList()

    const {getCinemaList} = useFilter(cinemaList,mytext)

    return <div>
            {/* {this.state.mytext} */}
                <input value={mytext} onChange={(evt)=>{
                    setmytext(evt.target.value)
                }}/>
                {
                    getCinemaList.map(item=>
                        <dl key={item.cinemaId}>
                            <dt>{item.name}</dt>
                            <dd>{item.address}</dd>
                        </dl>    
                    )
                }
        </div>
}

十、React 路由

1. 什么是路由?

路由是根据不同的 url 地址展示不同的内容或页面。
一个针对React而设计的路由解决方案、可以友好的帮你解决React components 到URl之间的同步映射关系。

2. 路由安装

react 路由 安装文档
注:最新的版本为6,后续会补充。

//安装语句
npm install react-router-dom@5

3. 路由的使用

3.1 路由导入

import React from "react"; 
import {BrowserRouter as Router, Switch,Route, Link} from "react-router-dom"; 

3.2 定义路由以及从定向

<HashRouter>
	<Switch>
		<Route path="/films" component={ Films}/>
		<Route path="/cinemas" component={ Cinemas}/>
		<Route path="/center" component={ Center}/>
		<Redirect from="/" to="/films" />
		{/* <Redirect from="/" to="/films" exact/>
		<Route path="*" component={NotFound}/> */}
	</Switch>
</HashRouter>

注意:

  1. exact 精确匹配 (Redirect 即使使用了exact, 外面还要嵌套Switch 来用)
  2. Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack,这个警告只有在hash 模式会出现。

3.3 嵌套路由

<Switch>
	<Route path="/films/nowplaying" component={ Nowplaying}/>
	<Route path="/films/comingsoon" component={ Comingsoon}/>
	<Redirect from="/films" to= "/films/nowplaying"/>
</Switch>

3.4 路由跳转方式

  1. 声明式导航
<NavLink to="/films" activeClassName= "active">films</NavLink>
<NavLink to="/cinemas" activeClassName= "active">cinemas</NavLink>
<NavLink to="/center" activeClassName= "active">center</NavLink>
  1. 编程式导航
this.props.history.push(`/center`)

3.5 路由传参

this.props.history.push({ pathname : '/user' ,query : { day: 'Friday'} }) this.props.location.query.day
this.props.history.push({ pathname: '/user',state:{day : 'Friday' } }) 
this. props.location.state.day

3.6 路由拦截

<Route path="/center" render={() =>isAuth()?<Center/>:<Login/>} />

3.7 withRouter的应用与原理

import { withRouter } from "react-router"; 
withRouter(MyComponent); 
withRouter(connect( ...)(MyComponent))

4. 注意事项

4.1 反向代理

链接: 文档

npm install http-proxy-middleware --save
const { createProxyMiddleware } = require( 'http-proxy-middleware');
module.exports = function(app) { app. use(
'/api', createProxyMiddleware({
target: 'http://localhost:5000', changeOrigin: true,
		})
	);
};

4.2 css module

链接: 文档

全局
:global(.active){
XXXX
}

鸣谢

  1. React全家桶教程
  2. React官方中文文档
  3. React菜鸟教程
  • 9
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
React 是一个用于构建用户界面的 JavaScript 库。它被 Facebook 开发,用于构建 Facebook 和 Instagram 等应用程序的用户界面。React 通过组件化的方式构建用户界面,使得代码更易于维护和复用。下面是一个简单的 React 入门指南: 1. 安装 React React 可以通过 NPM 安装,可以在命令行中执行以下命令来安装 React: ``` npm install react react-dom ``` 2. 创建 React 组件 在 React 中,一个组件就是一个 JavaScript 类。下面是一个简单的组件示例: ```javascript import React from 'react'; class HelloWorld extends React.Component { render() { return <div>Hello World!</div>; } } export default HelloWorld; ``` 这个组件只是简单地渲染一个 `Hello World!` 的文本。 3. 渲染组件 要在页面中渲染组件,需要使用 `ReactDOM.render()` 方法。下面是一个简单的示例: ```javascript import React from 'react'; import ReactDOM from 'react-dom'; import HelloWorld from './HelloWorld'; ReactDOM.render( <HelloWorld />, document.getElementById('root') ); ``` 这个示例中,我们首先导入了 `ReactDOM` 和 `HelloWorld` 组件,然后使用 `ReactDOM.render()` 方法将 `HelloWorld` 组件渲染到页面上。在这个示例中,我们将组件渲染到了一个 ID 为 `root` 的元素中。 这只是 React入门React 还有很多其他的概念和功能,比如 JSX、状态、生命周期等等。如果您想深入了解 React,可以查看 React 官方文档,里面有很多有用的信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值