React详解(包含新旧版本)

前言

React是一个用于构建用户界面的javaScript库,起源于facebook的内部项目,在13年f进行开源

17版本官网:React – A JavaScript library for building user interfaces

18版本官网:React 官方中文文档

特点:

  1. 声明式编码,组件化编码能提高开发效率和组件复用性
  2. React Native 编写原生应用
  3. 高效(优秀的Diffing算法)

一、基础

主要核心,依赖下面四个文件
    <!-- 引入核心库。全局出现React对象-->
    <script type="text/javascript" src="./React-js/16.8/react.development.js"></script>
    <!-- 用于支持react操作DOM。全局出现ReactDOM对象-->
    <script text="text/javascript" src="./React-js/16.8/react-dom.development.js"></script>
    <!-- 用于将jsx转换为js -->
    <script text="text/javascript" src="./React-js/16.8/babel.min.js"></script>
    <!-- 用于对组件标签属性进行限制。全局出现PropTypes对象 -->
    <script src="./React-js/16.8/prop-types.js"></script>

1、基本使用

1.1、虚拟dom

关于虚拟DOM:

  • 本质是object类型的对象(一般对象)
  • 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM 上:那么多的属性。
  • 虚拟DOM最终会被React转化为真实DOM。呈现在页面上
<body>
    <div id="test"></div>

    <!-- 引入核心库 -->
    <script type="text/javascript" src="./React-js/16.8/react.development.js"></script>
    <!-- 用于支持react操作DOM -->
    <script text="text/javascript" src="./React-js/16.8/react-dom.development.js"></script>
    <!-- 用于将jsx转换为js -->
    <script text="text/javascript" src="./React-js/16.8/babel.min.js"></script>

    <!-- 一定是babel -->
    <script type="text/babel">
        // 创建虚拟dom
        const VDOM = <h1>Hello.React</h1>
        const VDOM2 = <h1>----------------</h1>
        // 渲染虚拟DOM到页面(后面的会替换之前)
        ReactDOM.render(VDOM,document.getElementById('test'))
        ReactDOM.render(VDOM2,document.getElementById('test'))
    </script>
</body>

 2.2、JSX的语法规则

1、全称:  JavaScript XML。

2、react定义的一种类似于XMLJS扩展语法: JS + XML本质是React.createElement(componentprops, ...children)方法的语法糖

3、作用: 用来简化创建虚拟DOM

     写法:var ele = <h1>Hello JSX!</h1>

     注意1:它不是字符串, 也不是HTML/XML标签

     注意2:它最终产生的就是一个JS对象

  • 定义虚拟DOM时,不要写引号。
  • 标签中混入JS表达式时要用{}-
  • 样式的类名指定不要用class,要用className.
  • 内联样式,要用style={{key : value}}的形式去写。
  • 只有一个根标签
  • 标签必须闭合
  • 标签首字母
  • (1).若小写字母开头,则将改标签转为htm1中同名元素,若htm1中无该标签对应的同名元素,则报错。
  • (2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
<body>
    <div id="test"></div>

    <!-- 引入核心库 -->
    <script type="text/javascript" src="./React-js/16.8/react.development.js"></script>
    <!-- 用于支持react操作DOM -->
    <script text="text/javascript" src="./React-js/16.8/react-dom.development.js"></script>
    <!-- 用于将jsx转换为js -->
    <script text="text/javascript" src="./React-js/16.8/babel.min.js"></script>

    <!-- js写法 -->
    <script type="text/javascript">
        // 创建虚拟dom
        const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'js写法'))
        // 渲染虚拟DOM到页面(后面的会替换之前)
        ReactDOM.render(VDOM, document.getElementById('test'))
    </script>

    <!-- jsx写法 -->
    <script type="text/babel">
        const data = ['抽烟','喝酒','烫头']
        const obj = {name1:'抽烟',name2:'喝酒',name3:'烫头'}
        const myId = 'song'
        const myData = 'HELLO'
        const VDOM = (
            <div>
                <h1 className="box" id={myId}>
                    <span style={{ color: 'red', fontSize: '40px' }}>{myData.toLocaleLowerCase()}</span>
                </h1>
                <input type="text" />
                <ul>
                   {
                    // data   // 直接使用数组,会自动遍历
                    // obj     // 对象,会报错
                    data.map((item,i)=><li key={i}>{item}</li>)
                   }
                </ul>
            </div>

        )
        // 渲染虚拟DOM到页面(后面的会替换之前)
        ReactDOM.render(VDOM, document.getElementById('test'))
    </script>
</body>

2、函数式组件

 <script type="text/babel">
        // 定义函数组件
        function Demo() {
            console.log(this);  // 经过babel转化开启严格模式,this没有明确的调用,所以为undefined
            return <h2>函数定义的组件</h2>
        }
        // 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('test'))


        /*
        执行了ReactDOM.render( <MyComponent/>.......之后,发生了什么?
        1.React解析组件标签,找到了MyComponent组件。
        2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOW转为真实DOM,随后呈现在页面中。

        */
    </script>

3、类组件

<script type="text/babel">
        // 定义类组件
        class Demo extends React.Component{
            render(){
                // render:类的原型对象上(可在浏览器控制台输入Demo回车测试),供实例使用
                // this指向Demo的实例对象。俗称:组件对象或组件实例对象
                console.log('render中this',this);
                return (
                    <h2>类定义的组件</h2>
                )
            }
        }
        // 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('test'))


         /*
        执行了ReactDOM.render( <MyComponent/>.......之后,发生了什么?
        1.React解析组件标签,找到了MyComponent组件。
        2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用原型上的render方法
        3.将返回的虚拟DOW转为真实DOM,随后呈现在页面中。
        */
    </script>

3.1、constructor

<script type="text/babel">
        // 定义类组件
        class Weather extends React.Component {
            // 构造器是否接受props,是否传递super,取决于:是否需要在构造器中通过this访问props
            // 若是写了构造器,不给super传props则,在构造器中拿不到props。为undefined
            constructor(props) {    // 构造器只调用一次 
                // super(props)
                super()
                console.log(this.props);
            }
            render() {
                return (
                    <h2 ></h2>
                )
            }
        }

        const p = { name: 'tom', age: 18, sex: '女' }
        // 渲染组件到页面
        ReactDOM.render(<Weather {...p} />, document.getElementById('test'))
    </script>

4、组件的三大核心

4.1、state:存放状态

 <script type="text/babel">
        // 定义类组件
        class Weather extends React.Component {
            constructor(props) {    // 构造器只调用一次 
                super(props)
                // 初始化状态
                this.state = {
                    isHot: false,
                    wind: '大风'
                }
                // 改变原型上的demo的this指向,并把原型上的demo赋值到实例上的demo上。处理下面this为undefind
                this.demo = this.demo.bind(this)
            }
            render() {     // 调用1+n次。1:初始化   n:状态更新的次数
                return (
                    <h2 onClick={this.demo}>今天天气{this.state.isHot ? '炎热' : '很冷'}</h2>
                )
            }
            demo() {
                // demo放在哪里? Weather的原型对象上,供实例使用
                //由于demo是作为onclick的回调,所以不是通过实例调用的,是直接调用
                //类中的方法默认开启了局部的严格模式,所以demo中的this为undefined
                console.log('this', this); // undefind

                // 不能直接修改值。数据虽然变化,但页面不刷新
                // this.state.isHot = !this.state.isHot
                // 注意:需要通过setState方法来修改状态
                this.setState({ isHot: !this.state.isHot })
            }
        }
        // 渲染组件到页面
        ReactDOM.render(<Weather />, document.getElementById('test'))

        function demo() {
            // console.log('被点击');
            alert('被点击')
        }
    </script>
(1)、state简写
<script type="text/babel">
        // 定义类组件
        class Weather extends React.Component {
            // 初始化状态
            state = { isHot: false, wind: '大风' }

            render() {    
                return (
                    <h2 onClick={this.demo}>今天天气{this.state.isHot ? '炎热' : '很冷'}</h2>
                )
            }
            // 自定义方法---需要赋值语句的形式+箭头函数
            demo = ()=> {
                console.log('this', this); // undefind

                this.setState({ isHot: !this.state.isHot })
            }
        }
        // 渲染组件到页面
        ReactDOM.render(<Weather />, document.getElementById('test'))

    </script>
    <!-- 
        1、组件中 render方法中的this 为组件实例对象-
        2、组件自定义的方法中this为 undefined,如何解决?
                 a.强制绑定this:通过函数对象bind
                 b.箭头函数
        3、状态数据,不能直接修改或更新
     -->

 4.2、props:接收参数

 <script type="text/babel">
        // 定义类组件
        class Weather extends React.Component {

            render() {
                console.log(this);
                return (
                    <ul>
                        <li>姓名:{this.props.name}</li>
                        <li>性别:{this.props.sex}</li>
                        <li>年龄:{this.props.age}---{this.props.flag}</li>
                    </ul>
                )
            }
        }

        const p = { name: 'tom', age: 18, sex: '女' }

        // 渲染组件到页面
        ReactDOM.render(<Weather {...p} flag={666}/>, document.getElementById('test'))

    </script>
(1)、props限制

    <!-- 用于对组件标签属性进行限制。全局出现PropTypes对象 -->

    <script src="./React-js/16.8/prop-types.js"></script>

 <script type="text/babel">
        // 定义类组件
        class Weather extends React.Component {

            render() {
                console.log(this);
                // 注意:props是只读的
                this.props.speak()
                return (
                    <ul>
                        <li>姓名:{this.props.name}</li>
                        <li>性别:{this.props.sex}</li>
                        <li>年龄:{this.props.age+1}-----</li>
                    </ul>
                )
            }
        }
        // propTypes:给类加规则
        Weather.propTypes = {
            // 在15以及以下版本
            // name:React.PropTypes.string
            // 16版本及以上,需要通过引入PropTypes对象
            name:PropTypes.string,
            sex:PropTypes.string,
            age:PropTypes.number.isRequired,  // isRequired。必传
            speak: PropTypes.func  // 限制为函数
        }
        // 设置不传时的默认值
        Weather.defaultProps = {
            sex:'我是默认值'
        }

        // 渲染组件到页面
        ReactDOM.render(<Weather name="song" age={666} speak={fun}/>, document.getElementById('test'))

        function fun (){
            console.log('我是函数');
        }
    </script>
(2)、简写
<script type="text/babel">
        class Weather extends React.Component {
            // 写在类里面,相当于给类加属性
            static propTypes = {
                name: PropTypes.string,
                sex: PropTypes.string,
                age: PropTypes.number.isRequired,  // isRequired。必传
                speak: PropTypes.func  // 限制为函数
            }
            static defaultProps = {
                sex: '我是默认值'
            }
            
            render() {
                console.log(this);
                // 注意:props是只读的
                this.props.speak()
                return (
                    <ul>
                        <li>姓名:{this.props.name}</li>
                        <li>性别:{this.props.sex}</li>
                        <li>年龄:{this.props.age + 1}-----</li>
                    </ul>
                )
            }
        }


        // 渲染组件到页面
        ReactDOM.render(<Weather name="song" age={666} speak={fun} />, document.getElementById('test'))

        function fun() {
            console.log('我是函数');
        }
    </script>
(3)、在函数组件的使用
// 定义函数组件
        function Weather(props) {
            console.log(this, props);  // 经过babel转化开启严格模式,this没有明确的调用,所以为undefined
            return (
                <ul>
                    <li>姓名:{props.name}</li>
                    <li>性别:{props.sex}</li>
                    <li>年龄:{props.age + 1}-----</li>
                </ul>
            )
        }
        Weather.propTypes = {
            name: PropTypes.string,
            sex: PropTypes.string,
            age: PropTypes.number.isRequired,
        }
        Weather.defaultProps = {
            sex: '我是默认值'
        }


        // 渲染组件到页面
        ReactDOM.render(<Weather name="song" age={666} />, document.getElementById('test'))
(4)、在脚手架中使用需要单独下载

 

4.3、refs与事件处理

(1)、字符串形式的ref
        class Demo extends React.Component{
            // 展示左侧输入框的数据
            showData = ()=>{
                alert(this.refs.inp1.value)
            }
            // 展示左侧输入框的数据
            showData2 = ()=>{
                alert(this.refs.inp2.value)
            }
            render(){
                return(
                    <div>
                        <input ref="inp1" type="text" placeholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
                        <input ref="inp2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
                    </div>
                )
            }
        }
(2)、回调函数形式的ref
        class Demo extends React.Component {
            state = { isHot: true }
            // 展示左侧输入框的数据
            showData = () => {
                alert(this.inp1.value)
            }

            changWeacter = () => this.setState({ isHot: !this.state.isHot })

            /**
             * ref若是以下面内联函数的方式定义,它在更新过程中会别执行两次,
             * 第一次传入参数为null,第二次才是DOM元素
             * 因为每次渲染时都会创建新的函数实例,所以React清空旧的ref,被设置新的、
             * *
             * 不过可以把回调函数定义成class的绑定函数的方式可以避免,
             * 
            */
            saveInp = (c)=>{
                this.inp2 = c; console.log('绑定函数@', c)
            }
            render() {
                const { isHot } = this.state
                // 
                return (
                    <div>
                        <h1>今天天气{this.state.isHot ? '炎热' : '很冷'}</h1>
                        {/* c:input标签。相当于往Demo身上添加了inp1属性,把input标签赋值给它。内联函数方式*/}
                        <input ref={c => { this.inp1 = c; console.log('@', c) }} type="text" placeholder="点击按钮提示数据" />
                        {/* 使用class绑定函数的方式 */}
                        <input ref={this.saveInp} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
                        <button onClick={this.changWeacter}>点击切换天气</button>
                    </div>
                )
            }
        }
(3)、API形式的ref
class Demo extends React.Component {
            /** React.createRef
             * 存放被ref标识的节点,但只能单独存一个
             */ 
            myRef = React.createRef()
            myRef2 = React.createRef()

             // 展示左侧输入框的数据
             showData = () => {
                console.log(this.myRef,this.myRef2);
            }
            render() {
                // 
                return (
                    <div>
                        <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
                        <input ref={this.myRef2} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
                    </div>
                )
            }
        }

5、事件处理

class Demo extends React.Component {
            /** 
             * 1、通过onXxx属性指定事件处理函数(注意大小写)
            * (1)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件  ---为了处理兼容
            * (2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)    --- 为了高效
             * 2、通过event.target得到发生事件的DOM元素对象
             */
            myRef = React.createRef()
            myRef2 = React.createRef()

            // 展示左侧输入框的数据
            showData = () => {
                console.log(this.myRef, this.myRef2);
            }
            showData2 = (e) => {
                console.log(e.target.value);
            }
            render() {
                // 
                return (
                    <div>
                        <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
                        <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
                    </div>
                )
            }
        }

5.1、函数柯里化与高阶函数

/** 
        * *高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
        * 1.若A雨数,接收的参数是一个函数,那么A就可以称之为高阶函数。
        * 2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
        *   常见的高阶函数有: Promise、setTimeout、arr.map()等等
        * 
        * *函数的柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
        */

// 普通写法
function sum(a,b,c){
     return a+b+c
}
const res = sum(1,2,3)

// 柯里化写法
function sum(a){
   return (b)=>{
      return (c)=>{
         return a+b+c
      }
   }
}
const res = sum(1)(2)(3)
        /* 普通写法: */
        class Demo extends React.Component {
            state = {
                username:'',
                password:''
            }

            // 保存
            saveUsername = (e)=>{
                this.setState({username:e.target.value})
            }
            savePassword = (e)=>{
                this.setState({password:e.target.value})
            }

            // 展示左侧输入框的数据
            handleSubmit = (e)=>{
                e.preventDefault()  // 阻止默认行为
                const {username,password } = this.state
                console.log('用户名密码:',username,password);
            }
            render() {
                // 
                return (
                    <form onSubmit={this.handleSubmit}>
                        用户名:<input onChange={this.saveUsername} type="text" />
                        密码:<input onChange={this.savePassword} type="password" />
                        <button>登录</button>
                    </form>

                )
            }
        }

        /* 柯里化写法: */
        class Demo extends React.Component {
            state = {
                username: '',
                password: ''
            }

            // 保存。柯里化写法
            saveData = (dataType) => {
                console.log('dataType', dataType);
                return (e) => {
                    this.setState({ [dataType]: e.target.value })
                }
            }

            handleSubmit = (e) => {
                e.preventDefault()  // 阻止默认行为
                const { username, password } = this.state
                console.log('用户名密码:', username, password);
            }
            render() {
                // 
                return (
                    <form onSubmit={this.handleSubmit}>
                        用户名:<input onChange={this.saveData('username')} type="text" />
                        密码:<input onChange={this.saveData('password')} type="password" />
                        <button>登录</button>
                    </form>

                )
            }
        }

        /* 不使用柯里化写法: */
        class Demo extends React.Component {
            state = {
                username: '',
                password: ''
            }

            // 保存。
            saveData = (dataType, data) => {
                console.log('dataType', dataType, data);
                this.setState({ [dataType]: data })
            }

            handleSubmit = (e) => {
                e.preventDefault()  // 阻止默认行为
                const { username, password } = this.state
                console.log('用户名密码:', username, password);
            }
            render() {
                return (
                    <form onSubmit={this.handleSubmit}>
                        用户名:<input onChange={(e) => this.saveData('username', e.target.value)} type="text" />
                        密码:<input onChange={(e) => this.saveData('password', e.target.value)} type="password" />
                        <button>登录</button>
                    </form>

                )
            }
        }

6、生命周期

6.1、旧版(16版本以及以前)

 1. 初始化阶段: 由ReactDOM.render()触发---初次渲染

            1.constructor():构造器

            2.componentWillMount():组件初始化挂载前

            3.render():初始调用一次,每次更改状态都会调用

            4.componentDidMount():组件挂载完

 2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

            1.shouldComponentUpdate()

            2.componentWillUpdate()

            3.render()

            4.componentDidUpdate()

3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

            1.componentWillUnmount():组件卸载前调用

(1)、执行案例
        class Count extends React.Component {
            constructor(props) {
                console.log('constructor--构造器');
                super(props)
                // 初始化状态
                this.state = { count: 0 }
            }
            add = () => {
                this.setState({ count: this.state.count + 1 })
            }
            force = () => {
                // 不受shouldComponentUpdate影响
                this.forceUpdate()
            }
            // 卸载组件
            unload = () => {
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }


            // 组件初始化挂载前
            componentWillMount() {
                console.log('componentWillMount-组件初始化挂载前');
            }
            // 组件初始化挂载完
            componentDidMount() {
                console.log('componentDidMount');
            }


            // 组件卸载前
            componentWillUnmount() {
                console.log('componentWillUnmount');
            }


            /* 更新流程 */
            // 不写默认返回true。控制组件更新,需要返回true才能执行后面更新操作
            shouldComponentUpdate() {
                console.log('shouldComponentUpdate----------控制组件更新阀门');
                return true
            }
            // 组件更新前
            componentWillUpdate() {
                console.log('componentWillUpdate------------组件更新前');
            }
            // 组件更新完
            componentDidUpdate() {
                console.log('componentDidUpdate-------------组件更新完');
            }

            render() {
                console.log('render');
                const { count } = this.state
                return (
                    <div>
                        <h2>当前的求和为:{count}</h2>
                        <button onClick={this.add}>点击+1</button>
                        <button onClick={this.force}>强制更新</button>
                        <button onClick={this.unload}>卸载</button>
                    </div>
                )
            }
        }
        
        ReactDOM.render(<Count />, document.querySelector('#test'))
(2)、父子组件Render
        // 父组件
        class A extends React.Component {
            state = { carName: '奔驰' }
            chang = () => {
                this.setState({ carName: '牛马' })
            }
            render() {
                return (
                    <div>
                        <h2>A</h2>
                        <button onClick={this.chang}>换车</button>
                        <B carName={this.state.carName}></B>
                    </div>
                )
            }
        }
        // 子组件
        class B extends React.Component {
            // 第一次不会执行,只有更新后才执行
            componentWillReceiveProps(props) {
                console.log('componentWillReceivProps', props);
            }

            /* 更新流程 */
            // 不写默认返回true。控制组件更新,需要返回true才能执行后面更新操作
            shouldComponentUpdate() {
                console.log('shouldComponentUpdate----------控制组件更新阀门');
                return true
            }
            // 组件更新前
            componentWillUpdate() {
                console.log('componentWillUpdate------------组件更新前');
            }
            // 组件更新完
            componentDidUpdate() {
                console.log('componentDidUpdate-------------组件更新完');
            }
            render() {
                console.log('render');
                return (
                    <div>
                        <h2>B---{this.props.carName}</h2>
                        <div></div>
                    </div>
                )
            }
        }
       
        ReactDOM.render(<A />, document.querySelector('#test'))

6.2、新版(17版本已经之后)

1. 初始化阶段: ReactDOM.render()触发---初次渲染

  1. constructor()
  2. getDerivedStateFromProps
  3. render()
  4. componentDidMount()

2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

  1. getDerivedStateFromProps:俗称派生钩子
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate:俗称快照钩子
  5. componentDidUpdate()

3. 卸载组件: ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount()
(1)、getDerivedStateFromProps

static getDerivedStateFromProps(props, state) {

                console.log('getDerivedStateFromProps', props, state);

                // 如果返回一个对象,则相当于修改了state,

                // return props

                // return null

   }

默认返回 null

该钩子会导致代码冗余,并使组件难以维护,很少使用。

若state的值在任何时候都取决于props,那么可以使用该生命周期钩子。

(2)、getSnapshotBeforeUpdate

可在componentDidUpdate钩子第三个参数接受到返回值 

     class Count extends React.Component {
            constructor(props) {
                console.log('constructor----------',props);
                super(props)
                // 初始化状态
                this.state = { count: 0 }
            }
            add = () => {
                this.setState({ count: this.state.count + 1 })
            }
            force = () => {
                // 不受shouldComponentUpdate影响
                this.forceUpdate()
            }
            // 卸载组件
            unload = () => {  
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }

            // 若state的值在任何时候都取决于props,那么可以使用该生命周期钩子
            static getDerivedStateFromProps(props, state) {
                console.log('getDerivedStateFromProps', props, state);
                // 如果返回一个对象,则相当于修改了state,不在具有响应式
                // return props
                return null
            }

            // 俗称快照。可在componentDidUpdate钩子第三个参数接受到返回值
            getSnapshotBeforeUpdate(){
                console.log('getSnapshotBeforeUpdate');
                return null
                // return 'song'
            }

            // 组件初始化挂载前----------18已经弃用
            // componentWillMount() {
            //     console.log('componentWillMount-----组件初始化挂载前');
            // }
            componentDidMount() {
                console.log('componentDidMount------------------------组件初始化挂载完');
            }

            componentWillUnmount() {
                console.log('componentWillUnmount----------------组件卸载前');
            }

            /* 更新流程 */
            // 不写默认返回true。控制组件更新,需要返回true才能执行后面更新操作
            shouldComponentUpdate() {
                console.log('shouldComponentUpdate--------------更新阀门');
                return true
            }
            // 组件更新前----------18已经弃用
            // componentWillUpdate() {
            //     console.log('componentWillUpdate------------组件更新前');
            // }
            componentDidUpdate(preProps,preState,snapshotValue) {   // (旧的,旧的,快照返回值)
                console.log('componentDidUpdate-------------组件更新完',preProps,preState,snapshotValue);
            }

            render() {
                console.log('render----------------',this);
                const { count } = this.state
                return (
                    <div>
                        <h2>当前的求和为:{count}</h2>
                        <button onClick={this.add}>点击+1</button>
                        <button onClick={this.force}>强制更新</button>
                        <button onClick={this.unload}>卸载</button>
                    </div>
                )
            }
        }

        ReactDOM.render(<Count count={66} />, document.querySelector('#test'))
(3)、getSnapshotBeforeUpdate案例

每秒出现一条数据,但页面里面内容不根据超出部分滚动

       class NewList extends React.Component {
            state = {
                newsArr: []
            }
            componentDidMount() {
                setInterval(() => {
                    const { newsArr } = this.state
                    // 模拟一条新数据
                    const news = '新闻' + (newsArr.length + 1)
                    // es6语法,添加进行
                    this.setState({ newsArr: [news, ...newsArr] })
                }, 1000)
            }
            getSnapshotBeforeUpdate() {
                // 获取内容整体高度
                return this.refs.list.scrollHeight
            }
            componentDidUpdate(preProps, preState, height) {
                console.log(height);
                this.refs.list.scrollTop += this.refs.list.scrollHeight - height
            }

            render() {
                return (
                    <div className="list" ref="list">
                        {
                            this.state.newsArr.map((el, i) => {
                                return <div key={i} className="news">{el}</div>
                            })
                        }
                    </div>
                )
            }
        }

        ReactDOM.render(<NewList count={66} />, document.querySelector('#test'))

6.3、总结

  • 16版本:正常使用
  • 17版本:componentWillMount,componentWillUpdate,componentWillReceiveProps可以使用,但要在前加修饰符。例如:UNSAFE_componentWillUpdate
  • 18版本:componentWillMount,componentWillUpdate,componentWillReceiveProps被移除多,出两个新钩子getDerivedStateFromProps,getSnapshotBeforeUpdate

7、扩展 

7.1、react/vue中的key有什么作用?(key的内部原理)?

简单:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
详细:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
         1.旧虚拟DOM中找到了与新虚拟DOM相同的key:
               a.若虚拟DOM中内容没变,直接使用之前的真实DOM
               b.若虚拟DOM中内容变了,则生成新的真实DOM,替换页面中之前的真实DOM
         2.旧虚拟DOM中未找到与新虚拟DOM相同的key
              a.根据数据创建新的真实DOM,随后渲染到到页面

(1)、为什么遍历列表时,key最好不要用index?
         /* * 1。若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
         * 2.如果结构中还包含输入类的DOM:会产生错误DOM更新==>界面有问题。
         * 
         * 案例:本来只要更新小李这条数据的,因为使用索引做key,导致所有都要更新
         *    初始化数据:
         *        { id: 1, name: '小张', age: 18 },
         *        { id: 2, name: '小宋', age: 19 }
         *    初始化虚拟DOM:
         *        <li key=0 >小张--18</li>
         *        <li key=1 >小张--19</li>
         *   更新后的数据:往前面增加数据
         *        { id: 3, name: '小李', age: 66 },
         *        { id: 1, name: '小张', age: 18 },
         *        { id: 2, name: '小宋', age: 19 }
         *    初始化虚拟DOM:
         *        <li key=0 >小李--66</li>
         *        <li key=1 >小张--18</li>
         *        <li key=2 >小张--19</li>
         *   */
        class Person extends React.Component {
            state = {
                persons: [
                    { id: 1, name: '小张', age: 18 },
                    { id: 2, name: '小宋', age: 19 }
                ]
            }
            add = () => {
                const { persons } = this.state
                const p = { id: persons.length + 1, name: '小李', age: persons.length + 1 }
                this.setState({ persons: [p,...persons] })
            }

            render() {
                return (
                    <div>
                        <h2>展示人员信息</h2>
                        <button onClick={this.add}>添加人员小李</button>
                        <h2>使用索引值做key</h2>
                        <ul>
                            {
                                this.state.persons.map((obj, i) => {
                                    return <li key={i}>{obj.name}--{obj.age}<input type="txt" /></li>
                                })
                            }
                        </ul>
                        <h2>使用id做key</h2>
                        <ul>
                            {
                                this.state.persons.map((obj, i) => {
                                    return <li key={obj.id}>{obj.name}--{obj.age}<input type="txt" /></li>
                                })
                            }
                        </ul>
                    </div>
                )
            }
        }
        // 渲染虚拟DOM到页面(后面的会替换之前)
        ReactDOM.render(<Person />, document.getElementById('test'))

二、脚手架

安装遇到的问题:

场景:create-react-app 项目名 创建项目卡住,过一会报下面错误【error An unexpected error occurred: "https://registry.npm.taobao.org/axios: certificate has expired"】

原因:淘宝镜像原地址证书已于2024年1月22日过期

现已更换镜像地址为https://registry.npmmirror.com

【npm config set registry https://registry.npmmirror.com】

整体技术架构:react+webpack+es6+eslint

  • 全局安装: npm install -g create-react-app
  • 切换对应目录使用命令创建:create-react-app 项目名
  • 进入项目文件夹: cd 项目名
  • 启动:npm start

温馨提示:脚手架里面导入JS和JSX文件可以不写后缀

1、基本目录结构

public ---- 静态资源文件夹

favicon.icon ------ 网站页签图标

index.html -------- 主页面

logo192.png ------- logo图

logo512.png ------- logo图

manifest.json ----- 应用加壳的配置文件

robots.txt -------- 爬虫协议文件

src ---- 源码文件夹

App.css -------- App组件的样式

App.js --------- App组件

App.test.js ---- 用于给App做测试

index.css ------ 样式

index.js ------- 入口文件

logo.svg ------- logo图

reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持)

setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持)

2、模块化

2.1、组件的模块化

2.2、样式的模块化

(1)、方式一:使用module

(2)、方式二:使用scss、less等

3、配置代理

3.1、方式一:不能配置多个代理

优点:配置简单,前端请求资源时可以不加任何前缀。

注意:当前脚手架端口为3000。5000为服务器端口

在package.json文件中添加配置【 "proxy":"http://localhost:5000"】

在页面中请求服务器数据,必须把5000的端口改为3000,因为我们是在3000发送到3000,另一个3000的数据是从5000获取的。

另外这种方式不会把所有请求都转发都服务器,它把脚手架中public目录当根路径,会先在这个里面找,没有才会通过另一个代理的3000端口访问端口为5000的服务器

3.2、方式二:创建代理配置文件

在src下创建配置文件:src/setupProxy.js

  • 优点:可以配置多个代理,可以灵活的控制请求是否走代理。

  • 缺点:配置繁琐,前端请求资源时必须加前缀。

注意:在新版中使用旧版的配置方法:localhost会拒绝连接。无法访问

// 【旧版】
// const proxy_ = require('http-proxy-middleware')
// module.exports = function (app) {
//     app.use(
//         proxy_('/api1', {
//             target: 'http://localhost:5000',
//             changeOrigin:true, // 默认false
//             pathRewrite:{'^/api1':''}
//         })
//     )
// }


// 【新版】
const { createProxyMiddleware } = require('http-proxy-middleware') // 分开暴露的方式

module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api1', {  // 遇见【/api1】前缀的请求,触发此代理配置
            target: 'http://localhost:5000',   // 请求转发给谁
            /** 控制服务器收到的请求头中Hoos字段的值。默认为false
             *	前端代理加上changeOrigin:true,后台收到的为 localhost:5000 否则为 localhost:3000
             */
            changeOrigin: true,  
            pathRewrite: { '^/api1': '' },  // 重写请求路径。匹配路径中存在/api1的,替换为空
        }),
        createProxyMiddleware('/api2', {
            target: 'http://localhost:5001',
            changeOrigin: true,
            pathRewrite: { '^/api2': '' },
        })
    )
}

4、发布与订阅

  • 下载: pubsub-js

使用:

  • 引入:import PubSub from 'pubsub-js'  
  • 订阅:this.aa = PubSub.subscribe('dataObj',(_,data)=>{})
  • 发布:PubSub.publish('dataObj',{isLoading:false, err:err.message })
  • 取消订阅:PubSub.unsubscribe(this.aa)

三、react路由

react-router-dom在2021年11月升级到了6版本

1、旧版本【5】

npm i react-router-dom@5

(1)、HashRouter 与 BrowserRouter

HashRouter

  • 基于 hash 模式:页面跳转原理是使用了 location.hashlocation.replace,和 vue routerhash 模式实现一致。
  • 比较丑:在域名后,先拼接 /#,再拼接路径,也就是利用锚点,实现路由的跳转。如:www.dzm.com
  • #后面的不会发送到服务器。可以解决一些路径问题
  • 刷新页面会丢失路由state参数,没保存,

BrowserRouter

  • 基于 history 模式:页面跳转原理是使用了 HTML5 为浏览器全局的 history 对象新增了两个 API,包括 history.pushStatehistory.replaceState,和 vue routerhistory 模式实现一致。
  • 更加优雅: 直接拼接路径。如:http://www.dzm.com/xx
  • 后端需做请求处理:切换路由后,请求接口路径会发生变化,后端需要配合做处理。
  • 兼容:低版本浏览器可能不支持,目前市面上热门浏览器应该都支持了,不是特殊情况可以放心使用。(不兼容IE9及以下版本)
  • 刷新页面对路由state参数没影响,因为state保存在history对象里面

(2)、封装NavList标签

(3)、精准匹配与模糊匹配(默认)。重定向。Switch标签

精准匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

(4)、样式丢失问题。

问题复现:想在所有路由路径前面加固定前缀!

原因:react通过webpack中的devServer 在脚手架中的public为根路径开启一个内置服务器,请求啥给啥,如果没有会把index.html返回。

由此得知【http://localhost:3000/song/css/bootstrap.css】没有这个/song的目录

解决方案:

1、修改index.html

2、修改入口文件。

因为地址会出现#号,#号后面的会认为是前端资源不会在请求服务器

(5)、嵌套路由

(6)、路由传参

params 参数:参数在地址栏显示。

{/* 一、传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>

{/* 声明params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}></Route>


// 接收params参数
const { match: { params: { id, title } } } = this.props

search 参数:参数在地址栏显示。

{/* 二、传递search参数(类似Ajax的query) */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>

{/* 声明search参数,无需声明接收 */}
<Route path="/home/message/detail" component={Detail}></Route>


import qs from 'querystring'  // 以前自带,现在需要下载
// 接收search参数
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))  // 转码

state 参数:参数不在地址栏显示。

{/* 三、传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>

{/* 声明state参数,无需声明接收 */}
<Route path="/home/message/detail" component={Detail}></Route>

// 接收state参数
const { id, title } = this.props.location.state

(7)、编程式路由导航

借用history对象的api来操作路由的跳转、前进、后退

{/* 一、传递params参数 */}
this.props.history.push(`/home/message/detail/${id}/${title}`)

{/* 二、传递search参数(类似Ajax的query) */}
this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)

{/* 三、传递state参数 */}
this.props.history.push(`/home/message/detail`,{ id, title})

(8)、withRouter

作用:可在一般组件中使用路由组件的API。接收一个组件,返回一个新组件

withRouter

使用:

// 引入
import { withRouter } from 'react-router-dom'

class MyNavLink extends Component {
  render() {
    return (
      <div>
        <NavLink activeClassName="song" className="list-group-item" {...this.props} />
      </div>
    )
  }
}

// 使用
export default withRouter(MyNavLink)

2、新版【6】

  • 内置组件的变化:移除<Switch />,新增<Routes />
  • 语法的变化:component={About} 变为 element={<About />} 等
  • 新增多个hook:useParamsuseNavigateuseMatch
  • 官网推荐使用函数式组件,以后可能完全替代类式组件,现官网案例都是用函数式组件

(1)、一级路由【Routes、Route】,重定向【Navigate】

  • <Routes> 和 <Route>要配合使用,且必须用<Routes>包裹<Route>标签
  • <Route> 相当于if语句,其路径与当前URL匹配,呈现对应组件
  • <Route caseSensitive>:该属性指定匹配时是否区分大小写(默认为false)
  • 当URL发送变化时,<Routes>都会查看其所有子<Route>元素以找到最佳匹配
  • <Route>也可嵌套使用,可配合useRoutes()配置“路由表”,但需通过<outlet>组件来渲染其子路由。

Navigate:只要该组件被渲染,就会修改路径,切换视图

其中属性replace:默认push,

import React from 'react'
import { NavLink,Routes,Route, Navigate } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'

export default function App() {
  return (
    <div>
       // ...
        <div className="list-group">
          <NavLink className="list-group-item" to="/about">About</NavLink>
          <NavLink className="list-group-item" to="/home">Home</NavLink>
        </div>
       // ...
      
        <div className="panel-body">
          <Routes>
            <Route path="/about" element={<About />}></Route>
            <Route path="/home" element={<Home />}></Route>
            {/* 重定向。Navigate不仅可以写to,还可通过replace指定调转模式 */}
            <Route path="/" element={<Navigate to="/home" />}></Route>
          </Routes>
        </div>
       // ...
    </div>
  )
}

(2)、自定义高亮类名

通过函数方式接收内置的返回值

import React from 'react'
import { NavLink,Routes,Route, Navigate } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'

export default function App() {
  function handleClass({isActive}){
    console.log('isActive',isActive);
    return isActive ? 'list-group-item song':'list-group-item'
  }

  return (
    <div>
      // ...
      <div className="list-group">
        <NavLink className={handleClass} to="/about">About</NavLink>
        <NavLink className={handleClass} to="/home">Home</NavLink>
      </div>
      // ...
    </div>
  )
}

(3)、路由表

useRoutes:通过该Hook函数创建路由表

import React from 'react'
import { Routes,Route, useRoutes } from 'react-router-dom'
import routes from './routes/index'

export default function App() {
  // 根据路由表创建路由规则
  const element = useRoutes(routes)

  return (
    <div>
      // ...
       <div className="panel-body">
            {/* <Routes>
              <Route path="/about" element={<About />}></Route>
              <Route path="/home" element={<Home />}></Route>
              <Route path="/" element={<Navigate to="/home" />}></Route>
            </Routes> */}
            {element}
       </div>
       // ...
    </div>
  )
}

// pages/routes/index.js
import { Navigate } from 'react-router-dom'
import Home from "../pages/Home"
import About from "../pages/About"

export default [
    {
        path: '/about',
        element: <About />
    },
    {
        path: '/home',
        element: <Home />
    },
    {
        path: '/',
        element: <Navigate to="/home" />
    }
]

(4)、嵌套路由【Outlet】

Outlet:产生嵌套时,指定子路由的渲染位置

/* 【父】 */
import { useRoutes } from 'react-router-dom'
import routes from './routes/index'
export default function App() {
  // 根据路由表创建路由规则
  const element = useRoutes(routes)

  return (
    <div>
       {/* ... */}
       <div className="list-group">
          <NavLink className="list-group-item" to="/about">About</NavLink>
          {/* end:如果选择子级路由子级高亮,父级就不会高亮了 */}
          <NavLink className="list-group-item" end to="/home">Home</NavLink>
       </div>
       {/* ... */}
       <div className="panel-body">
            {element}
       </div>
        {/* ... */}
    </div>
  )
}

// pages/routes/index.js
import { Navigate } from 'react-router-dom'
import Home from "../pages/Home"
import About from "../pages/About"
import News from '../pages/News'
import Message from '../pages/Message'

export default [
    {
        path: '/about',
        element: <About />
    },
    {
        path: '/home',
        element: <Home />,
        children:[
            {
                path:'news',
                element: <News />
            },
            {
                path:'message',
                element: <Message />
            }
        ]
    },
    {   // 重定向
        path: '/',
        element: <Navigate to="/home" />
    }
]



/* 【子】 */
import React, { useState } from 'react'
import { NavLink, Outlet } from 'react-router-dom'
export default function Home() {

  return (
    <div>
      <h3>我是Home的内容</h3>
      <div>
        <ul className="nav nav-tabs">
          <li>
            {/*  to="/home/news"   可以不带父级路由路径 */}
            <NavLink className="list-group-item" to="news">News</NavLink>
          </li>
          <li>
            <NavLink className="list-group-item" to="message">Message</NavLink>
          </li>
        </ul>

        {/* 指定路由的位置 */}
        <Outlet />
      </div>
    </div>
  )
}

(5)、路由传参

  • useParams():接收params参数,对应5版本中match.params
  • useSearchParams():用于读取或修改当前位置的URL中的查询字符串。返回一个包含两个值的数组,分别为当前search参数,更新search参数
  • useLocation():获取当前location信息,对应5版本路由组件的loction属性
  • useMatch():返回当前匹配信息,对应5版本路由组件的match属性
/* 【传递参数】 */
import React, { useState } from 'react'
import { Link, Outlet } from 'react-router-dom'
export default function Message() {
    const [message] = useState([
        { id: '01', title: '消息1', content: '吃' },
        { id: '02', title: '消息2', content: '喝' },
        { id: '03', title: '消息3', content: '嫖' },
        { id: '04', title: '消息4', content: '赌' }
    ])
    return (
        <div>
            <ul>
                {
                    message.map((m) => {
                        return (
                            <li key={m.id}>
                                {/* params参数 */}
                                {/* <Link to={`details/${m.id}/${m.title}`}>{m.title}</Link>&nbsp;&nbsp; */}
                                {/* Search参数 */}
                                {/* <Link to={`details?id=${m.id}&title=${m.title}`}>{m.title}</Link>&nbsp;&nbsp; */}
                                {/* state参数 */}
                                <Link to={`details`} state={{
                                    id: m.id,
                                    title: m.title
                                }}>{m.title}</Link>&nbsp;&nbsp;
                            </li>
                        )
                    })
                }
            </ul>
            <hr />
            <Outlet></Outlet>
        </div>
    )
}


/* 【接收参数】 */
import React from 'react'
import { useParams, useMatch, useSearchParams, useLocation } from 'react-router-dom'

export default function Details() {
  /* params参数 */
  // const {id,title} = useParams()  // 方式一
  // const a = useMatch('/home/message/details/:id/:title')  // 方式二
  // console.log('a',a);


  /* Search参数: */
  // // 方式一  search:参数   setSearch:更新参数方法
  // const [search, setSearch] = useSearchParams()
  // console.log('search', search.get('id'));
  // const id = search.get('id')
  // const title = search.get('title')
  // // 方法二
  // const a = useLocation()
  // console.log(a);

  /* state参数: */
  const { state: { id, title } } = useLocation()
  return (
    <div>
      <h3>展示详情</h3>
      <ul>
        {/* <li><button onClick={() => setSearch('id=666&title=更新哦~')}>点击更新收到的search参数</button></li> */}
        <li>{id}</li>
        <li>{title}</li>
      </ul>
    </div>
  )
}



// pages/routes/index
export default [
    {
        path: '/about',
        element: <About />
    },
    {
        path: '/home',
        element: <Home />,
        children:[
            {
                path:'news',
                element: <News />
            },
            {
                path:'message',
                element: <Message />,
                children:[
                    {
                        // path:'details/:id/:title',  // params 参数
                        path:'details',  // Search与state 参数
                        element: <Details />
                    }
                ]
            }
        ]
    },
    {
        path: '/',
        element: <Navigate to="/home" />
    }
]

(6)、编程式路由导航【useNavigate】

import React, { useState } from 'react'
import { Link, Outlet, useNavigate } from 'react-router-dom'

export default function Message() {
    const [message] = useState([
        { id: '01', title: '消息1', content: '吃' },
        { id: '02', title: '消息2', content: '喝' },
        { id: '03', title: '消息3', content: '嫖' },
        { id: '04', title: '消息4', content: '赌' }
    ])

    const a = useNavigate()
    function showDetail(m) {
        // a('/about')   // 方案一
        a('/home/message/details',{  // 方案二。配置项暂时只支持下面两个
            replace:false,
            state:{
                id:m.id,
                title:m.title
            }
        })
    }
    return (
        <div>
            <ul>
                {
                    message.map((m) => {
                        return (
                            <li key={m.id}>
                                {/* state参数 */}
                                <Link to={`details`} state={{
                                    id: m.id,
                                    title: m.title
                                }}>{m.title}</Link>&nbsp;&nbsp;
                                <button onClick={()=>showDetail(m)}>查看详情</button>
                            </li>
                        )
                    })
                }
            </ul>
            <hr />
            <Outlet></Outlet>
        </div>
    )
}

(7)、其它Hook

(7.1)、useInRouterContext()
 // 判断当前是否在路由的上下文中
console.log('判断当前是否在路由里',useInRouterContext());
(7.2)、useNavigationType()
// 判断当前页面通过 【刷新(POP) 或 push(PUSH) 或 replace(REPLACE)】 进入到该页面的 
console.log('useNavigationType',useNavigationType());
(7.3)、useOutlet()
/** 呈现当前组件中渲染的嵌套路由组件
   * 如果嵌套路由没有挂载,则返回null
   * 如果嵌套路由已经挂载,则展示嵌套的路由对象
   */
  console.log('useOutlet',useOutlet());
(7.4)、useResolvedPath()
   /** 解析路由信息
   * 
   */
  console.log('useResolvedPath',useResolvedPath('/user?id=6&name=对对对#qwe'));

四、redux

安装:yarn add redux

中文文档: Redux | Redux 中文文档

作用:

  • redux是一个专门用于做状态管理JS(不是react插件库)
  • 它可以用在react, angular, vue等项目中, 但基本与react配合使用
  • 集中式管理react应用中多个组件共享的状态。(组件通信)
  • 能不用就不用, 如果不用比较吃力才考虑使用。

1、简单使用

src目录下创建redux目录

(1)、count.js文件

本质是处理数据的函数。

第一次调用时,是store自动触发的,传递的preState是undefined

const initState = 0
// preState:旧值    action:UI组件传递过来的数据
function countRedux(preState = initState, action) {
    // 从action对象中获取type,data
    const { type, data } = action
    // 根据type决定如何加工数据
    switch (type) {
        case 'add':
            return preState + data
        case 'minus':
            return preState - data
        default:
            return preState
    }
}
export default countRedux

(2)、store.js文件是redux的入口文件

/**
 * 该文件专门用于暴露一个store对象,整个应用只有一个store对象
 */

// 引入
import { createStore } from "redux";
import countRedux from './count'

// 
export default createStore(countRedux)

(3)、在src目录下index.js中进行store的监听

// 引入react核心库
import React from 'react'
// 引入ReactDom
import ReactDOM from 'react-dom'
// 引入App组件
import App from './App'
import store from './redux/store'

// 渲染App到页面
ReactDOM.render(
    <App></App>
    , document.getElementById('root'))

// 检测redux中状态的变化,只要变化,就调用render
store.subscribe(() => {
    ReactDOM.render(
        <App></App>
        , document.getElementById('root'))
})

(4)、在页面中使用

import React, { Component } from 'react'
import store from '../../redux/store'

export default class Count extends Component {

    // componentDidMount(){
    //     // 检测redux中状态的变化,只要变化,就调用render
    //     store.subscribe(()=>{
    //         this.setState({})
    //     })
    // }

    add = () => {
        const { value } = this.selectNumber
        // 通知redux改数据
        store.dispatch({ type: 'add', data: value * 1 })
    }
    // ......
}

2、完整使用

与上面没很大区别只是多了两个文件

(1)、constanit文件

用于定义常量,便于管理的同时防止单词写错

/**
 * 该模块是用于定义action对象中type类型的常量值。便于管理的同时防止单词写错
 */

export const ADD = 'add'
export const MINUS = 'minus'

(2)、count_action文件

为Count组件生成action对象

import { ADD, MINUS } from './constant'
export const addAction = data => ({ type: ADD, data })
export const minusAction = data => ({ type: MINUS, data })

(3)、count_reducer文件


import { ADD, MINUS } from './constant'

const initState = 0
function countRedux(preState = initState, action) {
    // 从action对象中获取type,data
    const { type, data } = action
    // 根据type决定如何加工数据
    switch (type) {
        case ADD:
            return preState + data
        case MINUS:
            return preState - data
        default:
            return preState
    }
}
export default countRedux

(4)、组件中使用

import React, { Component } from 'react'
import store from '../../redux/store'
import { addAction, minusAction } from '../../redux/count_action'
export default class Count extends Component {
    add = () => {
        const { value } = this.selectNumber
        // 通知redux改数据。【*1 强制类型转换】
        store.dispatch(addAction(value * 1))
    }
    // ......
}

3、异步action

下载中间件:yarn add redux-thunk

中间件版本区分

1、版本二【2.4.2】

2、版本三【3.1.1】

(1)、在redux的入口文件store使用

/**
 * 该文件专门用于暴露一个store对象,整个应用只有一个store对象
 */

// 引入createstore,专门用于创建redux中最为核心的store对象
import { createStore, applyMiddleware, legacy_createStore } from "redux";
// 引入为Count组件服务的reducer
import countRedux from './count_reducer'

// 引入redux-thunk.川于支持异步action
// import reduxThunk from 'redux-thunk'   // redux-thunk@2
import { thunk } from 'redux-thunk'   // redux-thunk@3

// 暴露
export default legacy_createStore(
    countRedux,
    applyMiddleware(thunk)
)

(2)、action文件中

/**
 * 该文件专门为Count组件生成action对象
 */
import store from './store'  // 可省略


// 同步action,就是指action的值为一般对象
import { ADD} from './constant'
export const addAction = data => ({ type: ADD, data })

// 异步action,就是指action的值为函数
export const asyncAction = (data,time) => {
    return ()=>{
        setTimeout(()=>{
            store.dispatch(addAction(data))
        },time)
    }
}

// 简写
// export const asyncAction = (data,time) => {
//     return (dispatch)=>{  // store会自动调用这个,可以少引入store
//         setTimeout(()=>{
//             dispatch(addAction(data))
//         },time)
//     }
// }

(3)页面中

store.dispatch(asyncAction(value * 1,500))

五、react-redux

安装:yarn add react-redux

1、明确两个概念:

     (1)、UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
     (2)、.容器组件:负责和redux通信,将结果交给UI组件。

2、注意:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入

1、基本使用

第一步创建容器组件

// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入store
// import store from '../../redux/store'
import { addAction, minusAction, asyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'


/* 【一般写法:】 */
function a(state) {
    return { count: state }
}
function b(dispatch) {  // 内置处理好了
    return {
        jia: (data) => dispatch(addAction(data * 1)),
        jian: (data) =>  dispatch(minusAction(data * 1)),
        asyncJia:  (data,date) => dispatch(asyncAction(data * 1,date))
    }
}

/** 创建并暴露一个Count的容器组件
 * a:映射状态,返回值是一个对象
 * b:映射操作状态的方法,返回值是对象
 *    connect自动帮我们传递了store,所以无需上面的引用
 */
export default connect(a, b)(CountUI)


/* 【简写:】 */
export default connect(
    state => ({ count: state }),
    /** 简写。 react-reudx 在API层面做了优化,帮我们处理dispatch */
    {
        jia: addAction,
        jian: minusAction,
        asyncJia: asyncAction
    }
)(CountUI)

 第二步注册容器组件

第三步,可以取消src目录下index.js的监听,因为插件内置监听了

第四步,在UI组件使用

// 通过this.props 可以取到
this.props.jia(value * 1)

2、完整用法

与上面基本相同,只是把容器组件和UI组件写在一起了

import React, { Component } from 'react'
import { addAction, minusAction, asyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'


// 定义UI组件
class Count extends Component {
    add = () => {
        const { value } = this.selectNumber
        // 通知redux改数据。【*1 强制类型转换】
        this.props.jia(value * 1)
    }
    addOdd = () => {
        const { value } = this.selectNumber
        if (this.props.count % 2 != 0) this.props.jia(value * 1)
    }
    addAsync = () => {
        const { value } = this.selectNumber
        this.props.asyncJia(value * 1,1000)
    }
    render() {

        return (
            <div>
                <h1>当前求和为:{this.props.count}</h1>
                <select ref={c => this.selectNumber = c}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                </select>
                <button onClick={this.add}>+</button>
                <button onClick={this.addOdd}>当前求和为奇数在加</button>
                <button onClick={this.addAsync}>异步加</button>
            </div>
        )
    }
}


// 创建并暴露一个Count的容器组件
export default connect(
    state => ({ count: state }),  // 映射状态
    {   // 映射操作状态的方法
        jia: addAction,
        jian: minusAction,
        asyncJia: asyncAction
    }
)(Count)

3、多个组件共享数据

(1)、combineReducers

汇总多个reducer

(2)、组件中使用

总reducer会交给store

4、redux开发者工具

在 Chrome 应用商店 中搜索redux下载

运行工具需要下载库:yarn add redux-devtools-extensino

在redux目录下的store.js使用

补充

1、setState的两种写法

(1)、函数式

setState(updater, [callback])

  • updater可以接收到state和props。
  • callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

(2)、对象式

setState(stateChange, [callback])

  • 是函数式的简写(语法糖)
  • callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
export default class setState extends Component {
    state = { count: 0 }
    add = () => {
        const { count } = this.state
        // 对象式
        // this.setState({ count: count + 1 })
        this.setState({ count: count + 1 },()=>{
            console.log('count-回调',this.state.count);
        })
        console.log('count-外层',count);

        // 函数式
        // this.setState((state, props) => {
        //     console.log(state, props);
        //     return { count: state.count + 1 }
        // })
    }
    render() {
        return (
            <div>
                <h1>当前求和为:{this.state.count}</h1>
                <button onClick={this.add}>点击加一</button>
            </div>
        )
    }
}

2、路由懒加载与Suspense标签

  • 通过React的lazy函数配合import()函数动态加载路由组件,路由组件代码会被分开打包
  • 通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面

3、Fragment

可以不用再页面多渲染一个真实的DOM节点,与vue中template类似

4、context

用于祖孙组件间通信

场景:在应用开发中一般不用context, 一般都用它的封装react插件

import React, { Component } from 'react'

// 创建Context对象
const MyContext = React.createContext()
const { Provider,Consumer } = MyContext

// 类组件
export default class A extends Component {
    state = { name: 'tom' }
    render() {
        return (
            <div>
                <h2>我是A组件</h2>
                <h4>我的用户名是:{this.state.name}</h4>
                <Provider value={{ name: this.state.name, age: '48' }}>
                    <B></B>
                </Provider>
            </div>
        )
    }
}
class B extends Component {
    render() {
        return (
            <div>
                <hr />
                <h2>我是B组件</h2>
                <C></C>
            </div>
        )
    }
}

// 【类组件中使用】
// class C extends Component {
//     // 声明接收祖组件的数据
//     static contextType = MyContext
//     render() {
//         console.log('this-C', this.context);
//         return (
//             <div>
//                 <hr />
//                 <h2>我是C组件</h2>
//                 <h4>我的用户名是:{this.context.name}---{this.context.age}</h4>
//             </div>
//         )
//     }
// }

// 【函数组件中使用】
function C() {
    return (
        <div>
            <h2>我是函数式-C组件</h2>
            <h4>我的用户名是:</h4>
            <Consumer>
                {
                    value =>{
                        // console.log(value);
                        return `${value.name},年龄是${value.age}`
                    }
                }
            </Consumer>
        </div>
    )
}

5、HookS

Hook是React 16.8.0版本增加的新特性/新语法。了解详情

作用:可让在函数组件中使用 state 以及其他的 React 特性

三个常用:

  • React.useState():让函数组件也可以有state状态, 并进行状态数据的读写操作
  • React.useEffect():用于模拟类组件中的生命周期钩子
  • React.useRef():进行ref标识
import React, { Component } from 'react'
import ReactDOM from 'react-dom'

function HookFun() {
    const [count, setCount] = React.useState(0)     // 定义状态
    const [name, setName] = React.useState('抽烟')  // 定义状态

    // 加的回调
    function add() {
        setCount(count + 1)  // 写法一
        // setCount(count => count + 1)  // 写法二
    }
    // 加的回调
    function hobby() {
        setName('喝酒')
    }
    // 卸载定时器回调
    function unmount(){
        ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    }


    // 相当于componentDidMount,页面初始化
    // React.useEffect(() => {
    //     console.log('componentDidMount');
    // }, [])
    // 相当于componentDidUpdate,监听name的改变
    // React.useEffect(() => {
    //     console.log('componentDidUpdate');
    // }, [name])
    // 相当于componentDidMount,卸载
    React.useEffect(() => {
        let timer = setInterval(() => {
            // setCount(count + 1)
            setCount(count => count+1)
            // console.log('卸载。。。');
        }, 1000)
        return () => clearInterval(timer)
    }, [])


    // 创建ref
    const myRef = React.useRef()
    function show(){
        console.log('myRef',myRef.current.value);
        alert(myRef.current.value)
    }

    return (
        <div>
            <input type="text" ref={myRef} />
            <h1>当前求和为:{count}</h1>
            <h1>爱好:{name}</h1>
            <button onClick={add}>点击加一</button>
            <button onClick={hobby}>点击改名</button>
            <button onClick={unmount}>卸载定时器回调</button>
            <button onClick={show}>点击提示输入框数据</button>
        </div>
    )
}
export default HookFun

6、组件性能优化

缺陷:

  • 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  • 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

思路:只有当组件的state或props数据发生改变时才重新render()。借助shouldComponentUpdate钩子

(1)、方案一:重写shouldComponentUpdate

比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

export default class A extends Component {
    state = { name: 'tom', age: 28 }

    change = ()=>{
        this.setState({ name: 'JOB', age: 38 })
        // this.setState({})
    }

    shouldComponentUpdate(nextProps,nextState){
        console.log('A---shouldComponentUpdate',nextProps,nextState);
        return !(this.state.name === nextState.name)
    }

    render() {
        console.log('A组件-render',this.state.name);
        return (
            <div>
                <h2>我是A组件</h2>
                <h4>我的用户名是:{this.state.name}</h4>
                <button onClick={this.change}>点击切换用户</button>
                {/* <B age={this.state.age}></B> */}
                <B age="99"></B>
            </div>
        )
    }
}
class B extends Component {
    shouldComponentUpdate(nextProps,nextState){
        console.log('B---shouldComponentUpdate',nextProps,nextState);
        return !(this.props.age === nextProps.age)
    }

    render() {
        console.log('B组件-render',this.props.age);
        return (
            <div>
                <hr />
                <h2>我是B组件---年龄为:{this.props.age}</h2>
            </div>
        )
    }
}

(2)、方案二:使用PureComponent

PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true

注意: 

  •  只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
  • 不要直接修改state数据, 而是要产生新数据。项目中一般使用PureComponent来优化
import React, { Component, PureComponent } from 'react'
export default class A extends PureComponent {
    state = { name: 'tom', age: 28,stus:['吃','喝','嫖'] }

    change = ()=>{
        this.setState({ name: 'JOB', age: 38 })
        // this.setState({})

        /* 赋值给obj:是引用地址的传递,PureComponent不会进行render */
        // const obj = this.state
        // obj.name = '卡拉米'
        // console.log(obj === this.state);
        // this.setState(obj)
    }
    addStu = ()=>{
        const {stus} = this.state

        /* 无效 */
        // stus.unshift('赌')
        // this.setState({stus})

        this.setState({stus:['堵',...stus]})
    }

    render() {
        console.log('A组件-render',this.state.name);
        return (
            <div>
                <h2>我是A组件</h2>
                <h4>我的用户名是:{this.state.name}</h4>
                <h4>数组:{this.state.stus}</h4>
                <button onClick={this.change}>点击切换用户</button>
                <button onClick={this.addStu}>往数组添加一个新属性</button>
                {/* <B age={this.state.age}></B> */}
                <B age="99"></B>
            </div>
        )
    }
}
class B extends PureComponent {

    render() {
        console.log('B组件-render',this.props.age);
        return (
            <div>
                <hr />
                <h2>我是B组件---年龄为:{this.props.age}</h2>
            </div>
        )
    }
}

7、renderProps(类似Vue插槽)

Vue中: 

  • 使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>

React中:

  • 使用children props: 通过组件标签体传入结构
  • 使用render props: 通过组件标签属性传入结构,可携带数据,一般用render函数属
export default class A extends Component {
    render() {
        return (
            <div>
                <h2>我是A组件</h2>
                {/* <B>对对对</B> */}

                {/* <B>
                    <C></C>
                </B> */}

                <B aa={(age) => <D age={age} />} render={(name) => <C name={name} />} />
            </div>
        )
    }
}
class B extends Component {
    state = { name: 'tom',age:'18' }
    render() {
        console.log('B:', this.props);
        return (
            <div className="b">
                <h2>我是B组件</h2>
                {/* <C></C> */}

                {/* {this.props.children} */}

                {/* 相当于Vue的插槽 */}
                {this.props.aa(this.state.age)}
                {this.props.render(this.state.name)}
            </div>
        )
    }
}
class C extends Component {
    render() {
        return (
            <div className="c">
                <h2>我是C组件---接收B传递的值:{this.props.name}</h2>
            </div>
        )
    }
}

class D extends Component {
    render() {
        return (
            <div className="d">
                <h2>我是D组件---接收B传递的值:{this.props.age}</h2>
            </div>
        )
    }
}

8、错误边界

getDerivedStateFromError:衍生钩子。子组件报错触发并携带错误信息,需返回一个对象

componentDidCatch:渲染组件时出错触发

理解:用于控制错误的返回,不让它影响其它组件

作用:用来捕获后代组件的错误,渲染出备用页面

特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用场景:项目上线后,数据渲染出现问题,由getDerivedStateFromError捕获错误,在componentDidCatch进行统计反馈给服务器,通知编码人员进行bug修改

// 父
export default class A extends Component {
    state = {
        hasError: ''   // 标识子组件是否产生错误
    }

    // 子组件出现错误时,会触发派生钩子调用,会携带错误信息
    static getDerivedStateFromError(err) {
        console.log('@err', err);
        return { hasError: err }
    }

    // 组件渲染过程中,由子组件出现了问题触发下面钩子
    componentDidCatch(error, info) {
        // 统计页面的错误。发送请求发送到后台去
        console.log('componentDidCatch',error, info);
    }

    render() {
        return (
            <div className='a'>
                <h2>我是父组件</h2>
                {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
            </div>
        )
    }
}


// 子
export default class A extends Component {
    // state = {name:'卡拉米'}  // 特意注释代码,引发错误

    render() {
        return (
            <div className='b'>
                <h2>我是子组件-child--{this.state.name}</h2>
            </div>
        )
    }
}

扩展

1fetch

了解详情

符合关注分离(Separation of Concerns)的原则。

关注分离理解(下面请求为例):请求数据后不在直接返回,细分为多个步骤

优点:

  • 原生函数,不再使用XmlHttpRequest对象提交ajax请求,直接在浏览器中使用
  • 也是promise风格

使用频率不高,因为不兼容低版本浏览器

fetch(url).then(   // 这一步then只是确认是否连接服务器
   response => {
      console.log('联系服务器成功')
      return response.json()   // json():这个json是它身上的函数,返回priomse
   },
   error => {
      console.log('联系服务器失败',error)
      return new Promise(()=>{})  // 返回一个新的promise实例来中断promise
   }
).then(   // 这里才是获取数据
   res => {
      console.log('获取数据成功',res)
   },
   error => {
      console.log('获取数据失败',error)
   }
)


/* 【优化一】 */
fetch(url).then(   
   response => {
      console.log('联系服务器成功')
      return response.json()   
   }
).then(   // 这里才是获取数据
   res => {
      console.log('获取数据成功',res)
   }
).catch(error => { console.log('请求出错',error) })


/* 【优化二】 */
try{
   const res = await fetch(url)
   const data = await res.json()
   console.log('获取数据成功',data)
} catch (err){
   console.log('请求出错',err)
}

每天补充部分

React Hooks 是 React 16.8 中新增的特性,它可以让你在函数组件中使用 state、生命周期钩子等 React 特性。使用 Hooks 可以让你写出更简洁、可复用且易于测试的代码。 React Hooks 提供了一系列的 Hook 函数,包括 useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect 和 useDebugValue。每个 Hook 都有特定的用途,可以帮助你处理不同的问题。 下面是 React Hooks 的一些常用 Hook 函数: 1. useState useState 是最常用的 Hook 之一,它可以让你在函数组件中使用 state。useState 接受一个初始状态值,并返回一个数组,数组的第一个值是当前 state 值,第二个值是更新 state 值的函数。 ``` const [count, setCount] = useState(0); ``` 2. useEffect useEffect 可以让你在组件渲染后执行一些副作用操作,比如订阅事件、异步请求数据等。useEffect 接受两个参数,第一个参数是一个回调函数,第二个参数是一个数组,用于控制 useEffect 的执行时机。 ``` useEffect(() => { // 这里可以执行副作用操作 }, [dependencies]); ``` 3. useContext useContext 可以让你在组件树中获取 context 的值。它接受一个 context 对象,并返回该 context 的当前值。 ``` const value = useContext(MyContext); ``` 4. useRef useRef 可以让你在组件之间共享一个可变的引用。它返回一个对象,该对象的 current 属性可以存储任何值,并在组件的生命周期中保持不变。 ``` const ref = useRef(initialValue); ref.current = value; ``` 5. useCallback useCallback 可以让你缓存一个函数,以避免在每次渲染时都创建一个新的函数实例。它接受一个回调函数和一个依赖数组,并返回一个 memoized 的回调函数。 ``` const memoizedCallback = useCallback(() => { // 这里是回调函数的逻辑 }, [dependencies]); ``` 6. useMemo useMemo 可以让你缓存一个计算结果,以避免在每次渲染时都重新计算。它接受一个计算函数和一个依赖数组,并返回一个 memoized 的计算结果。 ``` const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ``` 以上就是 React Hooks 的一些常用 Hook 函数,它们可以帮助你更好地处理组件状态、副作用、上下文和性能优化等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值