even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、react脚手架的安装

npm i create-react-app -g

创建react项目

creact-react-app 文件名称

注意:在项目中的package.json中的react-script是creact-react-app的核心部件,可以自动构建项目 , 以及编译,相当于webpack傻瓜版

注意:如果需要在项目中使用typescript,那么构建项目的语法是

create-react-app 文件名称 --template typescript

注意:在使用typescript进行react项目开发的时候,需要装的包初步列示如下,也就是说当使用typescript时除了安装所需要的包外,还需要安装声明文件,下面的redux与redux-thunk的声明文件内置了,所以不需要再安装,通常是@types/***, 并且类型声明一般只会在开发过程中使用,所以用--save-d

npm i redux redux-thunk react-redux @types/react-redux react-router-dom @types/react-router-dom

2、react关于jsx语法以及虚拟dom初识

jsx语法

import reactDom from 'react-dom'
/**
 * jsx即javascript + xml把js与html混写的一种语法
 * 可以在jsx上添加属性如id='mytest'
 * 注意如果有需要添加class这个时候就需要把属性名改为className如下
 * 如果想使用style,那么就需要用两个花括号进行使用,方法有同vue里的jsx语法
 * 如果想使用变量,那么就需要用一个花括号进行修饰如下,也可以修饰一个方法的调用
 *
 */
let name = 'bill'
reactDom.render(
    <div id='mytest' className='test' style={{'color': 'red'}}>this is test {name}</div>,
    document.getElementById('root') //注意:这里的节点的获取可以使用window.root进行替代
)

 在使用jsx进行编写的时候babel会在编译的时候把相当的语法转在一个对象,相当于react的虚拟dom对象

 注意:在编写style的时候,React接受一个采用小驼峰命名属性的 JavaScript 对象,而不是 CSS 字符串。这与 DOM 中 style 的 JavaScript 属性是一致的,同时会更高效的,且能预防跨站脚本(XSS)的安全漏洞

const divStyle = {
    color: 'blue',
    backgroundImage: 'url(' + imgUrl + ')',
};

虚拟dom

import react from 'react'
import reactDom from 'react-dom'

let name = 'bill'
let elem = react.createElement('div', {
    id: 'mytest',
    className: 'test',
    style: {'color': 'red'}
}, 'this is test ' + name, react.createElement('span', null, 'ok') )

//子类可以以数组的形式存在也可以以一个参数的形式存在,上面是以一个参数的形式存在,而下面是以一个数组的形式存在
// let elem = react.createElement('div', {
//     id: 'mytest',
//     className: 'test',
//     style: {'color': 'red'}
// }, ['this is test ' + name, react.createElement('span', null, 'ok')] )

reactDom.render(elem, document.getElementById('root'))

以上两种方法的效果是一致的,但是第一种方式更容易编写,层次更加分明

注意:在上面第二种方法中的elem中可以获取到属性props, 这个props对象里面有所对应的该节点的属性,同时里面的个children对象,这个children对象包含着所有的子对象,元素的更新,这个时候就会触发react里的diff算法,元素里与之前dom是相同的,那么就不会更新,如果是不想同的,就会触发更新, 如下面的例子

import react from 'react'
import reactDom from 'react-dom'

setInterval(() => {
    let elem = react.createElement('div', null, 
        react.createElement('span', null, '时间是:'),
        new Date().toLocaleTimeString())
    reactDom.render(elem, document.getElementById('root'))
}, 1000)

以上的例子中,span里的内容是时间是:这个时候更新的只会是时间部份的内容,其他内容不会被更新

 3、react组件

我们把页面拆分成若干个独立的部份, 单独编写,单独维护,组件分成两种: 一种是函数式组件,一种是类组件

函数式组件

   特点: 1、一个返回普通React元素的函数就是一个合法的React组件; 2、组件需要返回一个并且仅能返回一个React根元素; 3、组件的名称必须以大写字母开头

import reactDom from 'react-dom'

//也可以使用解构赋值的形式{name, age}作为形参
let Welcome = (props) => {
    return <div className='container'>
        <span>我的名字是</span>
        <span>{props.name}</span>
        <span>我的年龄是</span>
        <span>{props.age}</span>
    </div>
}

// reactDom.render(<Welcome name='bill' age='12'/>, document.getElementById('root'))
//如果需要引入一个对象的所有值作为所有的属性,那么就可以写作如下的方式
let obj = {name: 'bill', age: 12}
reactDom.render(<Welcome {...obj}/>, document.getElementById('root'))

类组件

import react from 'react'
import reactDom from 'react-dom'

class Welcome extends react.Component {
    render() {
        return <div className='container'>
            <span>我的名字是</span><span>{this.props.name}</span>
            <span>我的年龄是</span><span>{this.props.age}</span>
        </div>
    }
}

// reactDom.render(<Welcome name='bill' age='12'/>, document.getElementById('root'))
//如果需要引入一个对象的所有值作为所有的属性,那么就可以写作如下的方式
let obj = {name: 'bill', age: 12}
reactDom.render(<Welcome {...obj}/>, document.getElementById('root'))

 注意点: react中相当于vue中的template的写法

//注意如果不需要最外面套一层外层,那么就可以使用以下的两种表达方式,这两种表达方式是一个意思 
class Welcome extends react.Component {
    render() {
        // return <><span>name:</span>{this.props.name}<span>age:</span>{this.props.age}</>
        return <react.Fragment><span>name:</span>{this.props.name}<span>age:</span>{this.props.age}</react.Fragment>
    }
}
let obj = {name: 'bill', age: 12}
reactDom.render(<Welcome {...obj}/>, document.getElementById('root'))

 注意: 如果能用函数组件,就使用函数式组件,比较节省性能

函数式组件与类组件的搭配使用

import react from 'react'
import reactDom from 'react-dom'

const Test = props => {
    return <>
        <h1>{props.title}</h1>
        <button onClick={props.onClick}>按钮</button>
    </>
}

class Count extends react.Component{
    clickEvent = () => {
        console.log('are you ok????')
    }
    render() {
        return <div className='container'>
            <Test title='这个是标题' onClick={this.clickEvent}/>
        </div>
    }
}

reactDom.render(<Count />, document.getElementById('root'))

从受控程度来讲,组件分为

1、非受控组件: 是指DOM元素的值存在DOM元素的内部,不受React控制

2、受控组件:是指DOM元素的值受React状态控制

 4、状态

  • 组件的数据来源主要有两个地方, 分别是属性对象和状态对象
  • 属性是父组件传递过来的(默认属性, 属性校验)
  • 状态是自己内部的,改变状态的唯一方式就是setState, 注意setState这个方法是异步的
  • 属性和状态的变化都会影响到视图的更新
import react from 'react'
import reactDom from 'react-dom'

class Clock extends react.Component {
    constructor(props) {
        super(props)
        this.state = {
            date: new Date().toLocaleTimeString()
        }
    }
    componentDidMount() {  //react的生命周期函数  相当于vue里的mounted
        this.timer = setInterval(() => {
            //注意这个方法主要传达两层意思, 一层是改变数据, 一层是刷新视图层
            this.setState({date: new Date().toLocaleTimeString()})
        }, 1000)
    }

    render() {
        return <div>{this.state.date}</div>
    }
}
reactDom.render(<Clock />, document.getElementById('root'))

注意:进行state刷新的时候,要使用setState方法,这样才可以刷新视图层,如果是直接赋值,则不会刷新视图层,当然还有一种方法可以达到这种效果,就是this.state.date = new Date().toLocaleTimeString()进行调用后,再使用this.forceUpdate()进行强制页面更新也可以达到类似的效果, 这个方法是react.Component中定义的

setState方法的特性

setState这个方法是异步的

clickEvent = () => {
    // this.setState({count: this.state.count + 1})
    // this.setState({count: this.state.count + 1})
    //如果是调用上面的两个方法的话,那么执行该方法后,目标变量只会增加1,因为setState是异步, 注意setState这个方法里的函数接收两个参数,一个是prevState, prevProps
    // this.setState(state => ({count: state.count + 1}))
    // this.setState(state => ({count: state.count + 1}))
    //使用回调的方式可以实现在函数内部变量的引用,有效的避免上面的问题,但是外部还是异步的
    this.setState({count: this.state.count + 1}, () => {
        console.log(this.state.count)
    })
    //也可以使用回调的方式实现同步实现
}

 setState方法如果在进行赋值的时候,没有提及的不会被覆盖,其内部实现了提及的更新,或增量更新,而未提及的不动,如果需要进行删除的功能,可以使用undefined或null进行覆盖

函数式组件改变state的方法

import React, {Component, useState} from 'react';
import ReactDom from 'react-dom'

class Index extends Component {
    render() {
        return (
            <div>
                <Count/>
            </div>
        );
    }
}

//函数式组件, 关于变量的改变引起视图层的变化的方法
const Count = () => {
    let [count, setCount] = useState(100); //返回的是一个值,以及改变这个值的方法
    const add = () => {
        setCount(++ count);
    }
    return <div>
        <span>{count}</span>
        <button onClick={() => add()}>按钮</button>
    </div>
}

ReactDom.render(<Index/>, window.root)

注意:还有一种騒操作

this.state.count ++;  //先更新数据
this.setState({}) //实现页面刷新

state在函数式组件中使用的时候有三个注意点

注意点一

import ReactDom from 'react-dom'
import {useRef, useState} from "react";

/**
 * 当快速的点击增加后,再点击输出count的时候,会发现count的值并非是最新的,这个时候就需要用useRef进行辅助
 * @returns {*}
 * @constructor
 */
const NoticeOne = () => {
    let [count, setCount] = useState(100)
    let ref = useRef()
    const add = () => {
        setCount(++count)
        ref.current = count;
    }
    const logVal = () => {
        setTimeout(()  => {
            console.log(count)  //输出的值并非是正确的
            // console.log(ref.current) //输出的值是正确的
        }, 2000)
    }
    return <div>
        <h3>{count}</h3>
        <button onClick={add}>加</button>
        <button onClick={logVal}>输出</button>
    </div>
}

ReactDom.render(<NoticeOne/>, window.root)

注意点二

import ReactDom from 'react-dom'
import {useState} from "react";

/**
 * 在setCount中使用函数解决异步问题
 * @returns {*}
 * @constructor
 */
const NoticeOne = () => {
    let [count, setCount] = useState(100)
    const add = () => {
        setCount(++count)
    }
    const minus = () => {
        setTimeout(()  => {
            setCount(num => --num) //使用异步可以解决信息不同步问题
        }, 2000)
    }
    return <div>
        <h3>{count}</h3>
        <button onClick={add}>加</button>
        <button onClick={minus}>减</button>
    </div>
}

ReactDom.render(<NoticeOne/>, window.root)

注意点三:函数式组件中设置对象

import ReactDom from 'react-dom'
import {useState} from "react";

/**
 * 在setCount中使用函数解决异步问题
 * @returns {*}
 * @constructor
 */
const NoticeOne = () => {
    let [state, setState] = useState({name: 'yfBill', count: 100})
    const add = () => {
        setState({
            ...state,
            count: ++state.count
        })
    }
    const minus = () => {
        setState({
            ...state,
            count: --state.count
        })
    }
    return <div>
        <h3>{state.name}{state.count}</h3>
        <button onClick={add}>加</button>
        <button onClick={minus}>减</button>
    </div>
}

ReactDom.render(<NoticeOne/>, window.root)

 注意点四:在react18后,如果要使用setState的同步机制,那么需要按如下的方法进行处理

  public changeTitleEvent(): void {
    flushSync(() => {
      this.setState({
        title: 'ok!!!',
      });
    });
    console.log(this.state.title);
  }

// 那么这个时候this.state.title拿到的是更改后的数据, flushSync函数里的作法会被当作一次render统一执行

5、react组件的事件绑定

采用react进行事件绑定的时候,注意区别于原生的绑定事件,react绑定事件上采用的是驼峰的写法

在进行绑定的时候,在函数内部的this指向是个问题,有三个方案可以解决this的指向问题,具体如下例子

import react from 'react'
import reactDom from 'react-dom'

class List extends react.Component {
    clickEvent = () => {
        console.log('this指向', this)
    }
    render() {
        //注意这里的click事件的绑定要区别于原生的绑定事件,这里的绑定事件采用的是驼峰的写法
        /**
         * this 的指向修正方案
         * 方法一,采用bind方法进行this的修正,如用 onClick={this.clickEvent.bind(this)} 进行调用, 用这种方式,那么如果有参数,那么默认的ev会变成最后一个参数
         * 方法二,采用匿名函数的进行修正如: onClick={() => this.clickEvent()}进行调用,如果需要传参,那么就使用该方法,但是同是默认是没有ev的,需要ev  onClick={ev => this.clickEvent(ev)}
         * 方法三,采用ES7的语法进行调用,如本例子,默认会接收到ev, 这种方式不能传递参数
         */
        return (<react.Fragment>
            <div>this is list</div>
            <button onClick={this.clickEvent}>按钮</button>
        </react.Fragment>)
    }
}

reactDom.render(<List />, document.getElementById('root'))

 6、react的ref的使用

在react中现官方推荐使用的ref是调用react里的createRef()方法,这个方法会返回一个对象相当于{current: null},在虚拟dom进行编译的时候,检索到该对象的时候会把对应的值传到current上,这时就可以通过调用current获取到指定的dom对象

import react from 'react'
import reactDom from 'react-dom'

const Test = props => {
    return <>
        <h1>{props.title}</h1>
        <button onClick={props.onClick}>按钮</button>
    </>
}

class Count extends react.Component{
    constructor(props) {
        super(props)
        this.elem = react.createRef();
    }
    clickEvent = () => {
        console.log(this.elem) //输出 {current: div.container}
    }
    render() {
        return <div className='container' ref={this.elem}>
            <Test title='这个是标题' onClick={this.clickEvent}/>
        </div>
    }
}
reactDom.render(<Count />, window.root)

 注意:以上情况只是针对ref指向的是dom节点,如果ref指向的是类组件,那么获取的是类组件的实例,但是函数式组件需要套用react.forwardRef包一层才可以指定对应的ref如下例子

import react from 'react'
import reactDom from 'react-dom'

//函数组件
const Test = (props, ref) => { //用react.forwardRef包的函数式组件,接收两个参数,一个是props一个是ref对象
    return <div ref={ref}>
        <h1>{props.title}</h1>
        <button onClick={props.onClick}>按钮</button>
    </div>
}

//类组件
class Check extends react.Component {
    render() {
        return <div>this is check</div>
    }
}

const TestWrap = react.forwardRef(Test) //用react.forwardRef对函数式组件进行包一层

//也可以用以下的方法替代
// const TestWrap = react.forwardRef((props, ref) => {
//     return <div ref={ref}>
//         <h1>{props.title}</h1>
//         <button onClick={props.onClick}>按钮</button>
//     </div>
// })

class Count extends react.Component{
    constructor(props) {
        super(props)
        this.elem = react.createRef();
        this.check = react.createRef();
    }
    clickEvent = () => {
        console.log(this.elem, this.check) //输出 {current: div} {current: Check}
    }
    render() {
        return <div className='container'>
            <TestWrap title='这个是标题' onClick={this.clickEvent} ref={this.elem}/>
            <Check ref={this.check}/>
        </div>
    }
}

reactDom.render(<Count />, window.root)

 高阶组件的ref转发

import React, {
  FC,
  ReactElement,
  useRef,
  memo,
  ForwardedRef,
  forwardRef,
} from 'react';

interface IItemProps {
  ref: ForwardedRef<HTMLDivElement>;
}

const Item: FC<IItemProps> = memo(
  forwardRef<HTMLDivElement, IItemProps>(
    (props: IItemProps, ref): ReactElement => {
      return <div ref={ref}>this is Item</div>;
    },
  ),
);

const App: FC = (): ReactElement => {
  const inputRef = useRef<HTMLDivElement>(null);
  const clickEvent = () => {
    console.log(inputRef.current);
  };

  return (
    <div>
      <h1>this is App</h1>
      <Item ref={inputRef} />
      <button onClick={() => clickEvent()}>点击</button>
    </div>
  );
};

export default App;

 注意:在函数式组件中内部使用ref用的对象是useRef

import React, {Component, useRef} from 'react';
import ReactDom from 'react-dom'

class Index extends Component {
    render() {
        return (
            <div>
                <Item/>
            </div>
        );
    }
}

const Item = () => {
    let elem = useRef()
    const fn = () => {
        console.log(elem)  //这样就可以获得{current: h1}对象,就可以获得元素了
    }
    return <div>
        <h1 ref={elem}>this is item</h1>
        <button onClick={fn}>按钮</button>
    </div>
}

ReactDom.render(<Index/>, window.root)

7、在react中实现数据的双向绑定

import react from 'react'
import reactDom from 'react-dom'

class Comp extends react.Component {
    constructor(props) {
        super(props);
        this.state = {
            val: 'are you ok???',
            bindVal: 'yes'
        }
    }
    changeEvent = ev => {
        console.log(ev.target.value)
        this.setState({
            bindVal: ev.target.value
        })
    }
    render() {
        //在input中如果是默认的值可以defaultValue进行默认
        //在react中如果实现双重绑定用下在的第二种方法
        return <>
            <input type="text" defaultValue={this.state.val}/>
            <input type="text" value={this.state.bindVal} onChange={this.changeEvent}/>
            <button>点击</button>
        </>
    }
}

reactDom.render(<Comp/>, window.root)

 8、常见的知识点

import React from 'react';
import ReactDom from 'react-dom'

const App = () => {
    let str = '<h2>this is h2 content</h2>'
    return <div>
        <h1>this is App</h1>
        <div dangerouslySetInnerHTML={{__html: str}}></div>  //如果有需要按html的格式进行填充的,那么就需要该属性以这种方式填充
        <label htmlFor="abc">haha  //因为for是关键字,所以在react中用htmlFor进行替代
            <input type="text" id='abc'/>
        </label>
    </div>
}

ReactDom.render(<App/>, window.root)

fragment

在react中,根节点需要包一层元素,但是如果不需要这个元素可以使用fragment进行消除<fragment></fragment>有渲染后就会被替换掉, 也可以使用<></>替代,但是一种情况下不能用<></>替换,即遍历循环的时候,需要填充key, 这个时候就不能用这个语法糖

react的严格模式

react的StrictMode是一个用来突出显示应用程序中潜在问题的工具

  1. 与Fragment一样,StrictMode不会渲染任何可见的UI;
  2. 它为后代元素触发额外的检查和警告
  3. 严格模式检查仅在开发模式下运行;它们不会影响生产构建 

react的严格模式检查的是什么

  1. 识别不安全的生命周期
  2. 使用过时的ref api
  3. 检测意外的副作用 (组件会被调用两次,让用户查看是否代码会产生副作用)

9、react组件的懒加载

 

 Suspense是react里的组件

 

posted on 2021-03-26 13:26  even_blogs  阅读(237)  评论(0编辑  收藏  举报