React 使用 学习笔记

407 阅读14分钟

React 安装

安装命令(终端输入即可):npm i react react-dom

React基本使用

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>

2.创建React元素   

3.渲染React元素到页面中

<script>
//创建react元素 用 React.createElement(参数1,参数2,参数3);
//参数1:元素名称(标签)  参数2:元素属性 第三个及其以后的参数:元素的子节点(文本节点    const title = React.createElement('h1', {title:'我是标题',id:'h1'}, 'Hello  React' );
//渲染react元素 用ReactDOM.render(参数1,参数2)
//参数1:要渲染的react元素  参数2:挂载点(要把元素渲染到哪里)    ReactDOM.render(title, document.getElementById('root'));</script>

React脚手架的使用

1.使用react脚手架初始化项目

初始化项目,命令:npx create-react-app my-app

其中my-app是项目的名称 可以随便起名

启动项目,在项目根目录执行命令: npm start

2.在脚手架中使用react

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

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

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

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

JSX的基本使用

1.JSX简介

JSX是JavaScript XML的简写,表示在JavaScript代码里面写XML格式的代码

2.使用步骤

//1.使用JSX创建React元素   推荐使用小括号包裹JSX 从而避免js中自动插入分号陷阱
const title = (<h1>Hello JSX</h1> )
//2 渲染React元素
ReactDOM.render(title,root)

3.React元素的属性名采用驼峰命名法

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

4.嵌入JS表达式: {JavaScript表达式}  单括号

单大括号 可以使用任意JavaScript表达式 JS中的对象是一个例外,一般只会出现在style属性中

不能在大括号中出现语句

const name = 'Jack'
const dv = (
    <div>你好,我叫{name}</div>  //输出 你好,我叫Jack
)

5. JSX的列表渲染

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

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

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

  • 尽量避免使用索引号作为key

    const songs = [ {id:1, name:'痴心绝对'}, {id:2, name:'像我这样的人'}, {id:3, name:'南山南'}, ]

    const list = (

      {songs.map(item =>
    • {item.name}
    • }
    )

6.JSX的样式处理

  1)行内样式-style

const list = (  <h1 style={{color:'red',backgroundColor:'skyblue'}}>//第一个{}表示引入JS表达式,第二个{}表示对象    JSX的样式处理  </h1>)ReactDOM.render(list, document.getElementById('root'));

  2)类名-className(推荐)

import "./CSS/index.css" //title类样式写入index.css里

const list = (  <h1 className="title">    JSX的样式处理  </h1>)ReactDOM.render(list, document.getElementById('root'));

2.React组件基础

1.React组件的两种创建方式

(1)使用函数创建组件

  • 使用JS中的函数创建的组件叫做:函数组件

  • 函数组件必须有返回值

  • 组件名称必须以大写字母开头,React据此区分 组件 和 普通的React元素

  • 使用函数名作为组件标签名

    function Hello () { //函数名必须大写 return (

    这是我的第一个函数组件
    //必须有返回值,如果不渲染任何东西,返回null ) }

    const Hello = () =>

    这是我的第一个函数组件
    //箭头函数的写法 ReactDOM.render(,document.getElementById('root')) //单标签或者双标签都可以

(2)使用类创建组件

  • 使用ES6的class创建的组件

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

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

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

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

    class Hello extends React.Component { render() { return (

    这是我的第一个类组件
    //不想渲染任何东西 return null即可 ) } } ReactDOM.render( ,document.getElementById('root'))

2. 抽离为独立的JS文件

  1. 创建Hello.js

  2. 在Hello.js 中导入React

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

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

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

  6. 渲染组件

    //Hello.js

    import React from 'react' class Hello extends React.Component { render() { return (

    这是我的第一个抽离到JS文件中的组件
    ) } }export default Hello //导出Hello组件

    //index.js import Hello from './Hello' //渲染导入的Hello组件 ReactDOM.render( ,document.getElementById('root'))

3.React事件处理

1.事件绑定

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

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

class App extends React.Component {
    handleClick() {
        alert('触发了单击事件')
    }
    render() {
        return (
            <button onClick={this.handleClick}>点我,点我!!</button>
        )
    }
}
---------------------------------------------
//函数组件中
function App () {
    function handleClick() {
        console.log('单击事件触发了')
    }

    return (
        <button onClick={handleClick}>点我,点我!!</button> //函数组件中不加this
    )
}

2.事件对象

获取事件对象与之前的DOM一样,从事件处理程序中的参数就可以获取

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

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

function handleClick(e) {  //e就是事件对象--合成事件
    e.preventDefault();
}
<a onClick={handleClick}>点我,不会跳转页面</a>

4.有状态组件和无状态组件

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

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

状态(state)即数据

state的基本使用:状态是私有的,只能在组件内部使用。通过this.state来获取状态

class App extends React.Component {

    state = {
        count: 0
    }
    render() {
        return (
            <div>
                <h1>计数器:{this.state.count}</h1>
            </div>
        )
    }
}

setState()修改状态

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

注意:不要直接修改state的值,这是错误的

作用:1.修改state2.更新UI

//正确
this.setState({
    count:this.state.count + 1
})
//错误
this.state.count += 1

`````````````````````````````````````

class App extends React.Component {
    state = {
        count: 0
    }
    render() {
        return (
            <div>
                <h1>计数器:{this.state.count}</h1>
                <button onClick={() => {
                    this.setState({
                        count: this.state.count + 1
                    })
                }}>+1</button>
            </div>
        )
    }
}

5.事件绑定this指向

我们把事件函数尽可能抽离出来,在class里面写一个函数,而不是写在JSX里面,这样就比较条理有逻辑。但是要处理this的问题。因为写一个函数,事件处理函数里面的this指向的是调用者,而不是实例对象。

解决这个问题有三个办法

1)利用箭头函数没有this的特性

class App extends React.Component {
    state = {
        count: 0
    }
    
    onIncrement() {
        console.log(this);
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
            <div>
                <h1>计数器:{this.state.count}</h1>
                <button onClick={() => this.onIncrement()}>+1</button>
                //箭头函数不绑定this,所以它的this就是上一级的render的this,也就是类,所以this.onIncrement()的this就是类App
            </div>
        )
    }
}

2)bind方法

class App extends React.Component {

    constructor(){
        super(); //语法要求 构造函数里加上这一句
        this.state : {  //state的另一种写法,在constructor里面的写法
            count: 0
        }
        this.onIncrement = this.onIncrement.bind(this); //bind方法改变this指向,但不立即调用  constructor里面的this也是对象/类
    }
    onIncrement() {
        console.log(this);
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
            <div>
                <h1>计数器:{this.state.count}</h1>
                <button onClick={() => this.onIncrement()}>+1</button>
                
            </div>
        )
    }
}

3)class实例方法(推荐)

直接将事件处理程序写成箭头函数

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

class App extends React.Component {
    state = {
        count: 0
    }

    onIncrement = () => {  //直接写成箭头函数 这样他的this就是对象了
        console.log(this);
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
            <div>
                <h1>计数器:{this.state.count}</h1>
                <button onClick={() => this.onIncrement()}>+1</button>

            </div>
        )
    }
}

6.表单处理

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  //把value的值赋给state
        })
    }
    change
    render() {
        return (
            <div>
                <input type="text" value={this.state.txt} onChange={this.handleChange}></input>

            </div>
        )
    }
}

多表单元素优化:

如果有多个表单,那就要写多个事件处理程序,怪麻烦的。可以优化成只写一个事件处理程序

步骤:

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

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

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

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

//根据表单元素类型获取值
handleForm = (e) => {
    const target = e.target
    const value = target.type === 'checkbox'? target.checked : target.value
    const name = target.name
    this.setState({
        [name]:value
    })
}

非受控组件方式处理表单

借助于ref 使用原生DOM方式来获取表单元素值(仅了解)

React组件进阶

1.组件通讯

多个组件之间共享某些数据,通过组件的props实现

2.组件的props:

接受传递给组件的数据

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

<Hello name="jack" age={19} />  //传递非字符串类型的数据 要加个{}

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

//函数组件
function Hello(props) {
    console.log(props)
    return (
        <div>接收到数据:{props.name}</div>
    )
}

//类组件
class Hello extends React.compoent {
    render() {
        return (
            <div>接收到的数据:{this.props.age}</div>
        )
    }
}

props的特点:

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

  • props是只读的对象,只能读取,不能修改

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

    class Hello extends React.Component { constructor(props) { //构造函数比较特殊 需要在参数中写props super里也要出传递 super(props); } render() { return

    接收到的数据:{this.props.age}
    } }

3.组件通讯的三种方式

  1. 父组件传递数据给子组件

   2.子组件传递数据给父组件

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

3.兄弟组件

4.Context

跨组件传递数据

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

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

  (2)使用Provider组件作为父节点

<Provider>
    <div className="App">
        <Child1 />
    </div>
</Provider>

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

<Provider value="pink">

  (4)调用Consumer组件接收数据

<Consumer>
    {data => <span>data参数表示接收的数据 {data}</span>} //最近一层的Provider里面的value值会传递到参数data里面
</Consumer>

5.props深入

    1.children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性.Children属性与普通的props一样,值可以是任意值。(文本,React元素,组件甚至是函数)

function Hello (props) {
    return (
        <div>
            组件的子节点:{props.children}
        </div>
    )
}
<Hello>我是子节点</Hello>

   2.props校验

对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据。如果传入的数据格式不对,可能导致组件内部报错。

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

  3.props的默认值

6.组件的生命周期

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

只有类组件才有生命周期

生命周期的每个阶段都伴随着一些方法调用,这些方法就是生命周期的钩子函数。这些钩子函数为开发人员在不同阶段操作组件提供了时机

生命周期的三个阶段

创建时:挂载阶段

render里面不能再调用setState(因为setState方法就会调用render,再在render里调用setState就会无限递归循环下去);DOM操作要在componentDidMount里面进行,render constructor里面是不行的

更新时(更新阶段)

本阶段有两个钩子函数render()和componentDidUpdate()

render方法的触发方式有三种:调用setState()、调用forceUpdate()、组件接收到新的props。以上三者任意一种变化,都会重新渲染组件

如果直接调用setState,那么就会触发更新,然后执行render和componentDidUpdate,然后就开始无限循环递归。放在if里面的做法:

//比较更新前后的props是否相同,来决定是否重新渲染组件
componentDidUpdate(prevProps) {

//prevProps是上一次的props  this.props是这一次的
    if (prevProps.count !== this.props.count){  //count是props里的一个属性,这里的例子中判断count是否变化
        this.setState({});

        //发送ajax请求代码也在if里面
    }
}

 卸载时(卸载阶段)

7.render-props和高阶组件

如果两个组件中的部分功能相似或者相同,就需要复用相似的功能、复用什么?1state 2 操作state的方法

有两种方式:render-prop模式 和 高阶组件

render-props模式

import img from './image/cat.png' //注意react中使用图片的方法 先import

class Mouse extends React.Component {  //要复用的state和操作state的方法封装在Mouse里面
//这是一个获取鼠标位置的组件
    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)//props里传一个函数render,然后这里调用这个函数render,并把state传参进去 
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <h1>render props 模式</h1>
                <Mouse render={(mouse) => {     //传参 render函数,具体怎么实现在这里写
                    return <p>鼠标位置:{mouse.x} {mouse.y}</p>
                }} />

                <Mouse render={mouse => {        //传参 render函数,具体怎么实现在这里写
                    return <img src={img} alt="猫" style={{
                        position: 'absolute',
                        top: mouse.y - 64,
                        left: mouse.x - 64
                    }}></img>
                }} />
            </div>
        )
    }
}

高阶组件

//创建高阶组件
function withMouse (WrappedComponent) {
    //该组件提供复用的状态逻辑
    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);
        }

        componentWillMount(){
            window.removeEventListener('mousemove',this.handleMouseMove)
        }

        render() {
            return <WrappedComponent {...this.state} />
        }
    }

    return Mouse
}

const Position = props => (
    <p>
        鼠标当前位置:(x:{props.x}, y:{props.y})
    </p>
)
const MousePosition = withMouse(Position)


class App extends React.Component {
    render() {
        return (
            <div>
                <MousePosition></MousePosition>
            </div>
        )
    }
}

设置displayName:

原因:高阶组件在调试的时候还是显示同样的名字:比如上段代码中的组件,在调试的时候显示的还是. 原因:默认情况下,React使用组件名称作为displayName。

解决办法:为高阶组件设置displayName便于调试时区分

function withMouse (WrappedComponent) {
//省略Mouse里面的代码
    class Mouse extends React.Component{
    }
//displayName设置
    Mouse.dispalyName = `WithMouse${getDisplayName(WrappedComponent)}`

    return Mouse
}

function getDisplayName(WrappedComponent) {
    return WrappedComponent.dispalyName || WrappedComponent.name || 'Component'
}//尽量按照这种固定模式来写

const Position = props => (...  //略

const MousePosition = withMouse(Position)    //这样在调试的时候组件名显示为 WithMousePosition

传递props

React原理

1.setState()说明

setState更新数据是异步的,所以同一个函数里面后面的setState()不要依赖于前面的setState().同一个函数里可以调用多次setState(),但是只会触发一次重新渲染

this.state = {count:1}
this.setState({
    count:this.state.count + 1
})
console.log(this.state.count)  //1 

setState推荐语法:

为了解决异步的问题

this.setState((state,props) => {  //参数state表示最新的state  参数props表示最新的props
    return {
        count: state.count + 1
    }
})
//这种方法也是异步的 不过参数state可以拿到最新的

setState()的第二个参数:

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

语法:setState(update[,callback])  第二个参数为回调函数

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

2.组件更新机制

3.组件性能优化

这个钩子函数里面的参数nextProps  nextState是最新的值。要得到更新前的状态用this.state

 纯组件方式:

注意:纯组件方式 内部的对比是shallow compare(浅层对比),state中对于数值型数据来说,直接赋值,没有坑。但是对于引用类型来说,只比较对象的引用地址是否相同。如果只是修改了对象中的某个属性,但是对比发现内存地址没有变。所以,state或者props中属性值为引用类型时,应该创建新数据,不要直接修改原始数据。

虚拟DOM和Diff算法

组件的render()调用后,根据状态和JSX结构生成虚拟DOM对象。render方法并不意味着浏览器的重新渲染,仅仅说明要进行diff算法。

React路由

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

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

1.路由的基本使用

  1.安装: npm i react-router-dom

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

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

  3.使用Router组件包括整个应用(之后在被包括的部分才可以用Link和Route来匹配URL和组件)

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

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

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

完整案例:

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

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

const App = () => (
    <Router>
        <div>
            <h1>React路由基础</h1>
            <Link to="/first">页面一</Link>
            <Route path="/first" component={First}></Route>
        </div>
    </Router>
)

2.常用组件说明

  •   Router组件:包裹整个应用,一个React应用只需要应用一次
  •   两种常用的Router:HashRouter和BrowserRouter
  •   HashRouter:使用URL的哈希值实现(localhost:3000/#/first),只需要把BrowserRouter改成HashRouter,剩下的都不用改。不过一般不用
  • Link组件:用于指定导航链接(a标签)
  • Route组件:指定路由展示组件相关信息

3.路由的执行过程

4.编程式导航

通过JS代码来实现页面跳转

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

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

  • go(n):前进或者后退某个页面,参数n表示前进或者后退的页面数量(比如:-1表示后退到上一页)this.props.history.go()

    class Login extends Component { handleLogin = () => { //实现登录后跳转 // ... this.props.history.push('/home') } render() {...省略其他代码} }

5.默认路由

表示进入页面时就会匹配的路由

默认路由path为:/

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

6.匹配模式

  1.模糊匹配

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

模糊匹配规则:只要pathname(to后面的)以path(Route组件的path)开头 就会匹配成功

<Link to="/login">登录页面</Link>
<Route path="/" component={Home} /> //Link也会匹配成功这个Home

  2.精确匹配

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

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

<Route exact path="/" component=.../> 
//这种情况只能匹配pathname="/"这一种情况

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