当前位置: 首页 > article >正文

React基础知识一

写的东西太多了,照成csdn文档编辑器都开始卡顿了,所以分篇写。

1.安装React

需要安装下面三个包。

react:react核心包
react-dom:渲染需要用到的核心包
babel:将jsx语法转换成React代码的工具。(没使用jsx可以不装)

1.1 在html中引入CND

在html中使用react是一种简便的方式。非常有利于初学者学习。
地址为:https://zh-hans.react.dev/learn/installation
需要引入下面这三个CDN
注意:这三个顺序是不能变的

    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

1.2 React初体验

1.2.1 JSX写法(掌握)

在html里面添加下面的代码。注意一定要添加type="text/babel"不然是会不识别语法的。

<body>
    <div id="root"></div>
    <script type="text/babel">
      //1.创建虚拟DOM
      const VDOM=<h1>Hello React</h1>
      //或
      const VDOM = (//不加小括号换行也是不会报错的,仅仅是为了结构好管理
    	<h1>
      		<span>Hello React</span>
   	 	</h1>
      )
      //2.渲染虚拟DOM到页面
      ReactDOM.render(VDOM,document.getElementById('root'));
    </script>
</body>
1.2.2 React原始写法(了解)

下面这种写法可以不使用babel,也就不用引用babel包,所以类型也可以写成type=“text/javascript”。
这是react提供的语法糖,react自己的原始代码都是这种形式写的。对于开发者来说,这种语法是非常难用的。JSX就是react为我们提供的方便使用的语法。
下面的写法不用手动写,开发就用JSX,但是React会把JSX转化成下面的写法,了解一下还是很有必要的。

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

  <script type="text/javascript">
    //1.创建虚拟DOM
    const VDOM = React.createElement('h1',{id:"title"},"Hello React")
    //嵌套多层的时候非常的麻烦
    const VDOM2 =  React.createElement('h1',{id:"title"},React.createElement('span',{},"Hello React"))
    //2.渲染虚拟DOM到页面
    ReactDOM.render(VDOM, document.getElementById('root'));
    ReactDOM.render(VDOM2, document.getElementById('root2'));
  </script>
</body>
1.2.3组件写法(后面掌握)

这种写法可以先跳过,后面学组件的时候再说。

    <div id="root"></div>
    <script type="text/babel">
    
      function MyApp() {//这可以理解为一个组件
        return <h1>Hello, world!</h1>;//不要奇怪,这是jsx语法,就是支持这样写的
      }

      const container = document.getElementById('root');
      const root = ReactDOM.createRoot(container);//18以前这个函数名字叫render
      root.render(<MyApp />);

    </script>

运行一下,就有hello world了。
在这里插入图片描述
上面的代码可以这样写,实现一样的效果。

      const container = document.getElementById('root');
      const root = ReactDOM.createRoot(container);//18以前这个函数名字叫render
      root.render(<h1>Hello, world!</h1>);

1.3 真实DOM和虚拟DOM的区别

我们把虚拟DOM直接输出,可以看到虚拟DOM就是一个普通的js对象。

        const VDOM = React.createElement('h1', { id: "title" }, "Hello React")
        console.log("虚拟DOM:",VDOM);

在这里插入图片描述
真实DOM可以通过下面的方式输出。

        const realDOM=document.getElementById("root")
        console.log("真实DOM:",realDOM);

可以看到在浏览器直接把内容给输出了。
在这里插入图片描述
这时候我们需要debugger断点来看看他的真实内容是怎么样的。
可以看到,真实DOM是有非常多的属性的,而虚拟DOM的属性实际上非常的少。
在这里插入图片描述
或者使用console.dir也是可以直接输出的。g

console.dir(realDOM);

小结:
1.虚拟DOM本质上是一个普通对象。
2.虚拟DOM比较轻,真实DOM比较重,因为react不需要那么多属性。
3.虚拟DOM最终被react转化为真实DOM,呈现在页面上。

2.JSX语法规则

1.定义虚拟DOM时,不要写引号

const VDOM=<h1>Hello React</h1>

2.标签中引入JS表达式需要用{}
不需要加引号,直接跟{}

    const myId="title"
    const myData="Hello World"
    const VDOM = (
    <h1 id={myId}>
      <span>{myData}</span>
    </h1>
    )

3.不允许写class这个属性,而要写className
这是因为class是一个ES6类的关键词,React为了避开这个,自己定义了className。

.title{
	background:orange
}
//直接跟类名
<h1 className="title">Hello React</h1>

4.style不能写成字符串的形式,而是要写成对象的形式
需要大括号包裹。注意下面的{{}}并不是类似vue的mustache语法,而是和变量外面要使用{}是一样的道理,里面嵌套的{}表示的是这是一个对象。

<h1 style={{color:'lightblue',fontSize='29px'}}>Hello React</h1>

5.虚拟DOM只能有一个根元素
6.标签必须闭合

<input type="text" />
或者
<input type="text"><input/>

7.标签首字母
(1).若小写字母开头,则将该标签转为html同名元素,若html中无该标签对应的同名元素,则报错
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
注意: 你写的这些div标签都是JSX标签,不是html标签,或者说JSX给你提供了类似html的语法,最终他会转变成html标签在浏览器展示。

3.循环列表

注意,循环只能使用map这种表达式,不可以用for循环,因为for循环是语句。
{}里面是只能写js表达式,不能写js语句,所以for和if判断都是不可以写的。

这里必须需要理解清楚表达式语句的区别
表达式: 表达式会产生一个值,这个值可以放在任意一个需要值的地方。

a
a+b
foo(1)
arr.map()
console.log("name")
function test(){}//方法也是表达式,也是有返回值的

语句(代码):

if(){}
for(){}
switch(){}
<body>
    <div id="root"></div>

    <script type="text/babel">
        const data = ['Angular', 'React', 'Vue']
        const VDOM = (
            <div>
                <h1>遍历列表</h1>
                <ul>
                   {
                    data.map((item,index)=>{//这种方式实现key是不太合适的,后面再说
                        return <li key={index}>{item}</li>
                    })
                   }
                </ul>
            </div>
        )
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('root'));
    </script>
</body>

4.组件编程

4.1 react浏览器插件安装

推荐使用edge浏览器。下载方便,注意需要把下面两个勾打上,不然本地路径图标是不会亮的。
在这里插入图片描述
f12刷新一下开发者工具可以看到Components和Profiler这两个功能模块。
在这里插入图片描述

4.2 函数式组件

需要注意的是函数名字需要大写,不然报错。render函数的第一个参数需要写成标签的形式,不然报错。

        <div id="root"></div>
        <script type="text/babel">
             function MyComponent(){
                return <div>我是一个函数式组件(适用于简单组件的定义)</div>
             }
            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

在这里插入图片描述

4.2.1 函数式组件this的指向

this指向是undefined,因为react在被babel翻译的时候开启了严格模式。

4.3 类式组件

        <div id="root"></div>
        <script type="text/babel">
             class MyComponent extends React.Component{
                render(){
                    return <div>这是一个类式组件(用于复杂的场景)</div>
                }
             }
            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

1.react解析MyComponent标签,找到MyComponent组件。
2.发现组件是类定义的,new出该类的实例,通过实例调用render方法。
3.将render方法返回的虚拟DOM转化为真实DOM,在浏览器上呈现。

render里面的this是组件实例对象。

5.组件实例三大属性之state

简单使用

props必写,不写报错。继承必须调用构造器,这是class规定的。直接通过this.state赋值就可以了。

        <div id="root"></div>
        <script type="text/babel">
             class MyComponent extends React.Component{
                constructor(props){//props必写,不写报错
                    super(props)//继承必须调用构造器,这是class规定的
                    this.state={isHot:false}
                }
                render(){
                    return <div>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
                }
             }
            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

回调事件

通过onClick指定点击函数。

        <div id="root"></div>
        <script type="text/babel">
             class MyComponent extends React.Component{
                constructor(props){//props必写,不写报错
                    super(props)//继承必须调用构造器,这是class规定的
                    this.state={isHot:false}
                }
                render(){
                    return <div onClick={changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
                }
             }
             function changeWeather(){
                console.log("被点击了");
             }
            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

修改state的值

点击事件虽然有了,但是如果我们想要通过下面的代码来修改state的值的话是会报错的。因为this是undefined。

             function changeWeather(){
                console.log("被点击了");
                this.state.isHot=false
             }

我们可以定义一个that变量,在构造器里面把this赋值给that,这样就可以在外面获取到组件实例对象了。

        <div id="root"></div>
        <script type="text/babel">
             let that
             class MyComponent extends React.Component{
                constructor(props){//props必写,不写报错
                    super(props)//继承必须调用构造器,这是class规定的
                    this.state={isHot:true}
                    that=this
                }
                render(){
                    return <div onClick={changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
                }
             }
             function changeWeather(){
                console.log("被点击了",that);
                // that.state.isHot=false
             }
            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

这种方式虽然可行,但是非常的不合理。因为写的代码都是零散的,没有组件的封装性。
试着把代码改成下面这个样子。这里存在非常多的问题。
1.onClick={this.changeWeather},需要写this.changeWeather而不是直接写changeWeather。这是因为现在changeWeather是写在类里面,而写类外面是直接调用。
2.render和constructor的this是组件的实例对象,而changeWeather的this却是undefined。

        <div id="root"></div>
        <script type="text/babel">
             class MyComponent extends React.Component{
                constructor(props){//props必写,不写报错
                    super(props)//继承必须调用构造器,这是class规定的
                    this.state={isHot:true}
                }
                render(){
                    return <div onClick={this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
                }
                changeWeather(){
                    console.log("被点击了",this);
                    // that.state.isHot=false
                }
             }

            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

正确的做法: (没搞懂)
添加this.changeWeather=this.changeWeather.bind(this)这一行就可以了。

        <div id="root"></div>
        <script type="text/babel">
             class MyComponent extends React.Component{
                constructor(props){//props必写,不写报错
                    super(props)//继承必须调用构造器,这是class规定的
                    this.state={isHot:true}
                    this.changeWeather=this.changeWeather.bind(this)
                }
                render(){
                    return <div onClick={this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
                }
                changeWeather(){
                    console.log("被点击了",this);
                    // that.state.isHot=false
                }
             }

            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

也可以写成下面这个样子:(没搞懂)

        <div id="root"></div>
        <script type="text/babel">
             class MyComponent extends React.Component{
                constructor(props){//props必写,不写报错
                    super(props)//继承必须调用构造器,这是class规定的
                    this.state={isHot:true}
                    this.demo=this.changeWeather.bind(this)
                }
                render(){
                    return <div onClick={this.demo}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
                }
                changeWeather(){
                    console.log("被点击了",this);
                    // that.state.isHot=false
                }
             }

            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

通过setState修改值

直接修改值是没有响应式的,值虽然修改成功了,但没有响应式。

        <div id="root"></div>
        <script type="text/babel">
             class MyComponent extends React.Component{
                constructor(props){//props必写,不写报错
                    super(props)//继承必须调用构造器,这是class规定的
                    this.state={isHot:true}
                    this.demo=this.changeWeather.bind(this)
                    console.log(this);
                    
                }
                render(){
                    return <div onClick={this.demo}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
                }
                changeWeather(){
                    console.log("被点击了",this);
                    // this.state.isHot=!this.state.isHot
                    this.setState({isHot:!this.state.isHot})
                }
             }

            //2.渲染虚拟DOM到页面
            ReactDOM.render(<MyComponent/>, document.getElementById('root'));
        </script>

setState这个方法是来自React.Component这个类的,可以通过原型链查看到。
在这里插入图片描述
setState会合并对象而不是替换对象。也就是可以指定属性进行修改。

state的精简写法(最正确的写法 )

1.首先,利用class的特性,写在类里面的属性会直接挂载到类实例对象上。构造器可以不用写,直接定义一个state属性就可以。
2.每定义一个点击回调函数都要在构造器里面写类似this.changeWeather=this.changeWeather.bind(this)这样的代码,非常的麻烦。可以利用箭头函数,会自动向上查找this的特性完美的处理this指向问题。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                return <div onClick={this.changeWeather}>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</div>
            }
            changeWeather = () => {
                this.setState({ isHot: !this.state.isHot })
            }
        }
        ReactDOM.render(<MyComponent />, document.getElementById('root'));
    </script>

小结

state是三大属性中最难最重要的。掌握了state,最难的骨头已经啃下来了。

6.组件实例三大属性之props

基本使用

非常简单,没什么问题。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                return (
                    <div>
                       <ul>
                          <li>姓名:{this.props.name}</li>
                          <li>年龄:{this.props.age}</li>
                        </ul>
                    </div>
                )
            }
            changeWeather = () => {
                this.setState({ isHot: !this.state.isHot })
            }
        }
        ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
    </script>

批量传递props

写成下面这样就可以用对象来批量传递属性了。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                return (
                    <div>
                       <ul>
                          <li>姓名:{this.props.name}</li>
                          <li>年龄:{this.props.age}</li>
                        </ul>
                    </div>
                )
            }
            changeWeather = () => {
                this.setState({ isHot: !this.state.isHot })
            }
        }
        const p={name:"Tom",age:18}
        ReactDOM.render(<MyComponent {...p} />, document.getElementById('root'));
    </script>

细节:
这里有个细节需要讲一下。{…p}并不是展开对象。
在纯js里面,下面这样的写法是错误的,对象是不能通过…展开的,数组可以,需要是可迭代对象。

        const p={name:"Tom",age:18}
        console.log(...p);

在纯js里面,下面的写法是正确的,这是es6创建对象的一种方式。相当于复制对象。

        const p={name:"Tom",age:18}
        const p2={...p}
        console.log(p2);

但如果是在react标签里面,{}是react的表达式符号,不是对象,…p是不能展开对象的,按照js的语法的话,但在react里面可以,这是babel和react帮我们实现的。

<MyComponent {...p} />

在react里面写下面的代码,不会报错,但也没内容,这是react做了处理的结果。

        const p={name:"Tom",age:18}
        console.log(...p);

props类型限制

在react15之前是不需要额外引用包的。
是直接写在react包里面的,后面抽离了出去。基本没人用React15了。也不会有这种写法了。

MyComponent.propTypes = {
            name: React.PropTypes.string
        }

新写法:react16以后
需要引入这个包。

<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>

类型定义和默认值,没什么好说的。

        MyComponent.propTypes = {
            name: PropTypes.string.isRequired,
            age: PropTypes.number,
            speak: PropTypes.func
        }
        MyComponent.defaultProps = {
            name: "Tom",
            age: 19
        }

props的精简写法

把原本写在外面的写到类里面,并加上static就行了。
加static相当于直接挂载到类本身上,而不是实例对象上。

        class MyComponent extends React.Component {
            state = { isHot: true }
            static propTypes = {
                name: PropTypes.string.isRequired,
                age: PropTypes.number,
                speak: PropTypes.func
            }
            static defaultProps = {
                name: "Tom",
                age: 19
            }
            render() {
                return (
                    <div>
                        <ul>
                            <li>姓名:{this.props.name}</li>
                            <li>年龄:{this.props.age}</li>
                        </ul>
                    </div>
                )
            }
            changeWeather = () => {
                this.setState({ isHot: !this.state.isHot })
            }
        }

构造器和props

构造器可以不写,但是写了必须写props和super,不然会出现在构造器里面调用this.props出现undefined的bug。这个官方文档里面明确写的。

            constructor(props){
                super(props)
                console.log(this.props);//不写和不传super,this.props是undefiend
            }

这种情况是非常少见的,几乎不会出现。一是你不用写构造器也可以在别的地方使用this.props,二是如果你写了构造器,那么参数的props是可以直接使用的,不需要使用this.props。

函数式组件中使用props

在函数式组件里面,我们只能玩玩props,state和refs是玩不了的,因为没有this。而且props之所以能玩也是因为函数能传参数这个特性。类型限制只能写在外面。

    <div id="root"></div>
    <script type="text/babel">
        function Person(props) {
            return (
                <div>
                    <ul>
                        <li>姓名:{props.name}</li>
                        <li>年龄:{props.age}</li>
                    </ul>
                </div>
            )
        }
        //类型限制只能写在外面
        Person.propTypes = {
            name: PropTypes.string.isRequired,
            age: PropTypes.number,
            speak: PropTypes.func
        }
        Person.defaultProps = {
            name: "Tom",
            age: 19
        }
        const p = { name: "Tom", age: 18 }
        ReactDOM.render(<Person name="Jack" age="20" />, document.getElementById('root'));
    </script>

7.组件实例三大属性之refs

7.1 基本使用(字符串类型的ref)

通过下面的代码使用基本功能:
1.点击按钮显示输入框内容。
2.输入框焦点离开,显示输入框内容。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                return (
                    <div>
                        <input id="input1" placeholder="请输入内容" />&nbsp;
                        <button onClick={this.showData}>点击显示内容</button>&nbsp;
                        <input id="input2" placeholder="" onBlur={this.showData2} />&nbsp;
                    </div>
                )
            }
            showData = () => {
                const input=document.getElementById("input1")
                alert(input.value)
            }
            showData2 = () => {
                const input=document.getElementById("input2")
                alert(input.value)
            }
        }
        ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
    </script>

上面代码是通过原生getElementById实现的,不太合适。我们希望通过react实现。react的refs就可以实现类似的功能。

react的refs是用来给元素打标识用的。

修改后的代码如下,非常的简单。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                return (
                    <div>
                        <input ref='input1' id="input1" placeholder="请输入内容" />&nbsp;
                        <button onClick={this.showData}>点击显示内容</button>&nbsp;
                        <input ref='input2' id="input2" placeholder="" onBlur={this.showData2} />&nbsp;
                    </div>
                )
            }
            showData = () => {
                // const input=document.getElementById("input1")
                const {input1}=this.refs
                alert(input1.value)
            }
            showData2 = () => {
                // const input=document.getElementById("input2")
                const {input2}=this.refs
                alert(input2.value)
            }
        }
        ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
    </script>

在这里插入图片描述
注意:这里获取到的refs的内容是真实DOM节点的内容。
我们输出this看下refs长什么样子。是一个键值对,key是ref的名字,value是元素的类型加元素的id。
没有写id的话,我都不知道后面会跟id。
在这里插入图片描述

7.2 回调形式的ref

字符串形式的ref是最简单的一种ref。但是官方不推荐使用了,因为存在效率问题。不过如果你用的是16.8版本的话,继续用也没什么大问题,毕竟用起来简单。

下面是回调函数的用法,个人觉得不好用,写起来冗余和麻烦。

    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                console.log(this);
                return (
                    <div>
                        <input ref={e=> this.input1 = e} id="input1" placeholder="请输入内容" />&nbsp;
                        <button onClick={this.showData}>点击显示内容</button>&nbsp;
                        <input ref={e=> this.input2 = e} id="input2" placeholder="" onBlur={this.showData2} />&nbsp;
                    </div>
                )
            }
            showData = () => {
                const { input1 } = this
                alert(input1.value)
            }
            showData2 = () => {
                const { input2 } = this
                alert(input2.value)
            }
        }
        ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
    </script>

7.3 回调形式ref的调用次数问题

这个在官方文档里面有提到,在写内联函数的情况下,第一次渲染的时候会调用一次,当页面刷新的时候,会调用两次,第一次内联函数的参数值是null(被重置了),第二次是正常值。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                return (
                    <div>
                        <span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span>
                        <input ref={e => { this.input1 = e; console.log("@:", e); }} id="input1" placeholder="请输入内容" />&nbsp;
                        <button onClick={this.showData}>点击显示内容</button>&nbsp;
                    </div>
                )
            }
            showData = () => {
                this.setState({
                    isHot: !this.state.isHot
                })
            }
        }
        ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
    </script>

在这里插入图片描述
写成绑定函数的形式就不会出现这种问题了,但这个问题是无关紧要的。下面的这种写法非常的麻烦,完全没有必要。 用内联回调函数的形式是完全没有问题的。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            render() {
                return (
                    <div>
                        <span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span>
                        <input ref={this.saveInput} id="input1" placeholder="请输入内容" />&nbsp;
                        <button onClick={this.showData}>点击显示内容</button>&nbsp;
                    </div>
                )
            }
            showData = () => {
                this.setState({
                    isHot: !this.state.isHot
                })
            }
            saveInput = (e)=>{
                this.input1=e
                console.log("@:",e);
                
            }
        }
        ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
    </script>

7.4 通过createRef创建ref (推荐)

这种方式是官方推荐的,用起来不是很麻烦,也解决了性能问题。不过最简单还是字符串的形式。不过发生性能问题就不好说了。字符串形式还是不要使用比较好。

    <div id="root"></div>
    <script type="text/babel">
        class MyComponent extends React.Component {
            state = { isHot: true }
            myRef1=React.createRef()
            myRef2=React.createRef()
            render() {
                return (
                    <div>
                        <span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span>
                        <input ref={this.myRef1} id="input1" placeholder="请输入内容" />&nbsp;
                        <button onClick={this.showData}>点击显示内容</button>&nbsp;
                        <input ref={this.myRef2} onBlur={this.showData2} id="input1" placeholder="请输入内容" />&nbsp;
                    </div>
                )
            }
            showData = () => {
                alert(this.myRef1.current.value)
            }
            showData2 = () => {
                alert(this.myRef2.current.value)
            }
     
        }
        ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
    </script>

react脚手架使用和搭建

后面的有些内容不用脚手架建立工程项目的话使用起来不是很方便。或者不知道怎么使用,也不想花费大量的时间研究可能用不上的使用方式。所以用工程项目还是最有性价比的选择。

手动搭建react项目

通过下面的命令创建一个脚手架项目:

npm install -g create-react-app
create-react-app react-demos

默认目录结构如下:
在这里插入图片描述
这些内容可以作为参考,但是现在我们把src下的内容全部删掉,因为我们想要重头搭建我们的项目。
在这里插入图片描述
这时候我们再运行会告诉我们没有index.js。这是因为webpack需要index.js作为程序的入口。
在这里插入图片描述
非常神奇的是,你只要添加一个空白index.js文件,项目就可以运行的,只不过页面是空白的。
我们往index.js添加下面的内容,其中#root是index.html中的根元素,webpack自己会取找。

import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<h1>Hello React</h1>);

跑起来我们就可以看到下面的内容。
在这里插入图片描述
我们可以定义一个App类组件来封装我们的内容。

import ReactDOM from "react-dom/client";
import React from "react";
class App extends React.Component {
  render() {
    return <h1>Hello React</h1>;
  }
}
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

也可以把App组件单独写到外面。

import React from "react";
class App extends React.Component {
  render() {
    return <h1>Hello React</h1>;
  }
}
export default App;
import ReactDOM from "react-dom/client";
import React from "react";
import App from "./App";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

我们可以定义自己的组件,然后在App.jsx里面引入

import React from "react";
class HelloReact extends React.Component {
  render() {
    return <h1>Hello React</h1>;
  }
}
export default HelloReact;

这样,我们的工程就搭建完成了。

import React from "react";
import HelloReact from "./HelloReact";
class App extends React.Component {
  render() {
    return (
      <div>
        <HelloReact />
      </div>
    );
  }
}
export default App;

通过eject命令查看webpack配置

我们可以看到react脚手架创建的项目实际上是通过react-scripts这个工具来运行的。而webpack的配置就在这个包里面的。

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },

可以看到webpack的配置在这个包下面。
在这里插入图片描述
react-scripts这个工具提供了eject命令来暴露webpack的配置,但是一般不推荐这么做。一是因为webpack的配置非常复杂且难懂,二是一般我们专注开发,不要花太多精力在这些东西上。

一定要查看和修改也是有办法的,执行下面的命令:

npm run eject

会弹出警告,问你要不要弹出配置文件,这个操作是不可逆的。
在这里插入图片描述
执行完后,会多出config和scripts两个文件夹。
在这里插入图片描述
scripts节点的内容也变成下面的内容。

  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js"
  },

整个package.json都变了。依赖什么都一股脑的写道里面,非常的乱。所以还是不要弹出配置比较好。

使用craco来管理配置

这是一个第三方工具。主流的工具,大家都在用。

组件的生命周期

生命周期是非常重要的内容,我们需要在正确的生命周期做正确的事情。
这么这个网站是react官方声明周期图的地址,16.8后,react在官方文档里面已经移除了到这个图的导航。
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

这个图有两个版本,一个简单版,一个显示不常用生命周期版本。如果只看简单版的话,react的生命周期还是比较简单的。
在这里插入图片描述
不常用生命周期:
在这里插入图片描述

组件挂载和componentDidMount方法

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        render() {
          console.log("render");
        }
        componentDidMount() {
          console.log("compoentDidMount");
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

组件先执行constructor,再执行render,再执行componentDidMount。
在这里插入图片描述

父子组件的componentDidMount方法

如果是父子组件的话,情况就复杂一点。

      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        render() {
          console.log("render");
          return <SubComponent />;
        }
        componentDidMount() {
          console.log("compoentDidMount");
        }
      }
      class SubComponent extends React.Component {
        constructor(props) {
          super(props);
          console.log("sub constructor");
        }
        render() {
          console.log("sub render");
        }
        componentDidMount() {
          console.log("sub compoentDidMount");
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));

父组件的componentDidMount是最后执行的。但父组件是先被挂载的。之所以会出现父组件的componentDidMount后执行是因为使用的是深度优先遍历算法。
在这里插入图片描述

更新数据时的生命周期和componentDidUpdate方法

    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          message: "this is message",
        };
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        changeMessage = () => {
          this.setState({
            message: "message changed",
          });
        };
        render() {
          console.log("render");
          return (
            <div>
              <h1>{this.state.message}</h1>
              <button onClick={this.changeMessage}>changeMessage</button>
            </div>
          );
        }
        componentDidMount() {
          console.log("compoentDidMount");
        }
        componentDidUpdate() {
          console.log("compoentDidUpdate");
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

更新数据的时候,render函数会被多次调用。并且再render执行完成后,都会调用componentDidUpdate方法。
在这里插入图片描述

更新数据时的生命周期和componentDidUpdate方法——子组件更新

    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        render() {
          console.log("render");
          return (
            <div>
              <SubComponent />
            </div>
          );
        }
        componentDidMount() {
          console.log("compoentDidMount");
        }
        componentDidUpdate() {
          console.log("compoentDidUpdate");
        }
      }

      class SubComponent extends React.Component {
        state = {
          message: "this is message",
        };
        constructor(props) {
          super(props);
          console.log("sub constructor");
        }
        changeMessage = () => {
          this.setState({
            message: "message changed",
          });
        };
        render() {
          console.log("sub render");
          return (
            <div>
              <h1>{this.state.message}</h1>
              <button onClick={this.changeMessage}>changeMessage</button>
            </div>
          );
        }
        componentDidMount() {
          console.log("sub compoentDidMount");
        }
        componentDidUpdate() {
          console.log("sub compoentDidUpdate");
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

可以看到。当子组件内容被修改的时候,只会调用,子组件的更新方法,父组件是不会调用更新方法的,也不会调用render。

在这里插入图片描述

更新数据时的生命周期和componentDidUpdate方法——父组件更新

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          message: "this is message",
        };
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        changeMessage = () => {
          this.setState({
            message: "message changed",
          });
        };
        render() {
          console.log("render");
          return (
            <div>
              <h1>{this.state.message}</h1>
              <button onClick={this.changeMessage}>changeMessage</button>
              <SubComponent />
            </div>
          );
        }
        componentDidMount() {
          console.log("compoentDidMount");
        }
        componentDidUpdate() {
          console.log("compoentDidUpdate");
        }
      }

      class SubComponent extends React.Component {
        constructor(props) {
          super(props);
          console.log("sub constructor");
        }
        render() {
          console.log("sub render");
        }
        componentDidMount() {
          console.log("sub compoentDidMount");
        }
        componentDidUpdate() {
          console.log("sub compoentDidUpdate");
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

父组件内容修改的时候,子组件的render和componentDidUpdate也是会一起调用的,也就是子组件会重新渲染,因为子组件可能依赖了父组件的数据。react选择直接重新渲染子组件。
在这里插入图片描述

组件卸载和componentWillUnmount方法

在组件被销毁的时候,componentWillUnmount会被调用。我们需要在componentWillUnmount做一些回收资源的事情。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          show: true,
        };
        constructor(props) {
          super(props);
          // console.log("constructor");
        }
        changeShow = () => {
          this.setState({
            show: false,
          });
        };
        render() {
          // console.log("render");
          const { show } = this.state;
          return (
            <div>
              {show && <SubComponent />}
              <button onClick={this.changeShow}>changeShow</button>
            </div>
          );
        }
      }
      class SubComponent extends React.Component {
        state = {
          show: true,
        };
        constructor(props) {
          super(props);
          console.log("sub constructor");
        }
        changeShow = () => {
          this.setState({
            show: false,
          });
        };
        render() {
          console.log("sub render");
          return <div>我是子组件</div>;
        }
        componentDidMount() {
          console.log("sub compoentDidMount");
        }
        componentDidUpdate() {
          console.log("sub compoentDidUpdate");
        }
        componentWillUnmount() {
          console.log("sub componentWillUnmount");
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

不常用的生命周期

不常用也是官方文档说的。
在这里插入图片描述

shouldComponentUpdate方法

如果shouldComponentUpdate返回false,那么组件的render和componentDidUpdate将不会被调用。这个可以用于一些性能优化的地方。禁止一些组件的重新渲染。
从图中也可以很明显看到,render和componentDidUpdate这两个函数是在shouldComponentUpdate之后执行的,如果shouldComponentUpdate返回false,那么后面的生命周期将不再执行。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          message: "this is message",
        };
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        changeMessage = () => {
          this.setState({
            message: "message changed",
          });
        };
        render() {
          console.log("render");
          return (
            <div>
              <h1>{this.state.message}</h1>
              <button onClick={this.changeMessage}>changeMessage</button>
            </div>
          );
        }
        componentDidMount() {
          console.log("compoentDidMount");
        }
        componentDidUpdate() {
          console.log("compoentDidUpdate");
        }
        shouldComponentUpdate() {
          return true;
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>
getsnapshotbeforeupdate方法

官方文档给了下面的例子。
https://zh-hans.legacy.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate

处理列表滚动的时候可以用到,是伪代码。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class ScrollingList extends React.Component {
        constructor(props) {
          super(props);
          this.listRef = React.createRef();
        }

        getSnapshotBeforeUpdate(prevProps, prevState) {
          // 我们是否在 list 中添加新的 items ?
          // 捕获滚动​​位置以便我们稍后调整滚动位置。
          if (prevProps.list.length < this.props.list.length) {
            const list = this.listRef.current;
            return list.scrollHeight - list.scrollTop;
          }
          return null;
        }

        componentDidUpdate(prevProps, prevState, snapshot) {
          // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
          // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
          //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
          if (snapshot !== null) {
            const list = this.listRef.current;
            list.scrollTop = list.scrollHeight - snapshot;
          }
        }

        render() {
          return <div ref={this.listRef}>{/* ...contents... */}</div>;
        }
      }
      ReactDOM.render(<ScrollingList />, document.getElementById("root"));
    </script>
  </body>

组件通信

组件通信父传子props

这个其实没什么好讲的,直接使用props就可以了

组件通信子传父

在vue或者uniapp里面,子传父有类似$emit,$on这样的发送接收方法。但在react里面,这些都是没有的。react实现的方式就是纯js的方式,是利用回调函数机制来实现的。
我们需要在子组件上加一个回调方法。

<AddCounter onAdd={(n) => this.handleAdd(n)} />

完整逻辑如下。这里需要注意的地方就是函数的命名规范。例如add方法,你可以在父子组件里面都全部取名叫add,如果你自己能够分得清的话。
命名规范建议是,点击事件用clickXXX,组件回调函数用onXXX,而父组件处理函数用handleXXX。

    <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          counter: 0,
        };
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        handleAdd = (num) => {
          this.setState({
            counter: this.state.counter + num,
          });
        };
        render() {
          const { counter } = this.state;
          return (
            <div>
              <div>计数结果是:{counter}</div>
              <AddCounter onAdd={(n) => this.handleAdd(n)} />
            </div>
          );
        }
      }
      class AddCounter extends React.Component {
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        clickAdd = (n) => {
          this.props.onAdd(n);
        };

        render() {
          return (
            <div>
              {/*要写箭头函数,不能直接执行 直接执行会在在渲染的时候就调用了 */}
              <button onClick={() => this.clickAdd(1)}>+1</button>
              <button onClick={() => this.clickAdd(5)}>+5</button>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

tabs切换的组件通信案例(简单实现版)

写在一个组件里面,实现基本功能。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style>
      .tabs {
        display: inline-block;
        padding: 10px;
      }
      .active {
        color: pink;
        border-bottom: 2px solid pink;
      }
    </style>
  </head>

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          tabs: [
            {
              name: "电脑",
            },
            {
              name: "手机",
            },
            {
              name: "电视",
            },
          ],
          curIndex: 0,
        };
        clickTab = (index) => {
          this.setState({
            curIndex: index,
          });
        };
        constructor(props) {
          super(props);
          console.log("constructor");
        }
        render() {
          const { tabs, curIndex } = this.state;
          return (
            <div>
              {tabs.map((item, index) => {
                return (
                  <span
                    className={`tabs ${index === curIndex ? "active" : ""}`}
                    key={index}
                    onClick={(e) => this.clickTab(index)}
                  >
                    {item.name}
                  </span>
                );
              })}
              <div>{tabs[curIndex].name}</div>
            </div>
          );
        }
      }

      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>
</html>

在这里插入图片描述

tabs切换的组件通信案例(组件封装版)

PureComponent类和memo高阶函数

这个类是React提供的封装好的优化类。只有组件的props或者state发生改变的时候,才会重新渲染组件。源码实现是通过浅层比较实现,也就是只比较对象的第一层。

对于函数式组件来说,是没有生命周期的,也就不能实现类似PureComponent这样的封装效果,但是React提供了memo函数来实现这个功能。

开发的时候,你是一定要用到这两个东西的,不知道的话你一定是一个新手。官方文档性能优化是有提到这两个东西的。

hooks

什么是hooks? (coderwhy)

hooks是react 16.8(2019年)出的新特性。
react有两种形式来创建组件——类式和函数式。在hooks之前类式组件就是react最主流的编程方式。 这个时候,函数式组件是非常鸡肋的,几乎没什么用。因为函数式组件不能保存数据状态,所以只能用于一些简单的展示的场景,传什么数据就展示什么数据(因为只有props是可以用的)。并且函数组件是没有生命周期的。

但因为函数式编程的思想在前端是普遍流行和推崇的。我猜想react团队在设计函数式组件的时候肯定已经想到了这个问题。但可能当时没有想到合适方式实现函数式组件的完整功能。

对于开发者来说,使用类式组件或者函数式组件来开发功能实际上都是无所谓,谁好用就用谁。但设计者为了实现函数式组件可以说是绞尽脑汁。至于设计出来的东西好不好用另说。但函数式组件的这条路是一定要走下去的。

还有一个促使hooks诞生的原因是类式组件存在一些缺点。例如类式不好对功能进行拆分。当然hooks本身是否存在别的缺点我们另说。class概念难以理解以及this的指向问题处理对于初学者来说都是比较麻烦的。

1.hooks是完全可选的,你不用hooks,用类式组件也是完全没有问题的。
2.hooks是100%向后兼容的。hook不包含任何破坏性改动。

3.hooks的代码比类组件相对少一些。

组件插槽

react没有slot这样的节点概念,因为他不需要。

通过props.children实现插槽效果

通过props.children就可以直接实现插槽的效果。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class NavBar extends React.Component {
        state = { isHot: true };
        render() {
          const { children } = this.props;
          return (
            <div className="content">
              <div className="left">{children[0]}</div>
              <div className="center">{children[1]}</div>
              <div className="right">{children[2]}</div>
            </div>
          );
        }
        changeWeather = () => {
          this.setState({ isHot: !this.state.isHot });
        };
      }
      NavBar.propTypes = {
        name: PropTypes.string.isRequired,
        age: PropTypes.number,
        speak: PropTypes.func,
      };
      NavBar.defaultProps = {
        name: "Tom",
        age: 19,
      };

      class Main extends React.Component {
        render() {
          return (
            <NavBar>
              <div>我是左边内容</div>
              <div>我是中间内容</div>
              <div>我是右边内容</div>
            </NavBar>
          );
        }
      }
      ReactDOM.render(<Main />, document.getElementById("root"));
    </script>
  </body>

是可以实现插槽效果的。
在这里插入图片描述
但是,如果只有一个元素的时候,children并不是一个数组,而是一个元素对象,这时候通过数组下标取元素就不对了,虽然控制台并不会报错。这是react就是这样设计的,不要问为什么。 存在弊端。

            <NavBar>
              <div>我是左边内容</div>
              {/*
              <div>我是中间内容</div>
              <div>我是右边内容</div>
              */}
            </NavBar>

在这里插入图片描述
props.children方式存在的问题:
1.children一会是数组一会是对象,容易出错。
2.children是通过下标来直接取元素的,可读性非常的差,容易取错。

通过props参数实现插槽效果

直接通过参数把元素内容传过去,是不是非常的神奇?
再react里面,直接在props里面传参数就可以实现插槽的效果。参数名称随便取。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
    <style>
      .content {
        display: flex;
        /* align-items: center;
        justify-content: space-around; */
        height: 50px;
        .left {
          width: 100px;
          background: pink;
        }
        .center {
          width: 100%;
          background: lightblue;
        }
        .right {
          width: 100px;
          background: lightgreen;
        }
      }
    </style>
  </head>

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class NavBar extends React.Component {
        render() {
          const { leftSlot, centerSlot, rightSlot } = this.props;
          console.log(this.props);

          return (
            <div className="content">
              <div className="left">{leftSlot}</div>
              <div className="center">{centerSlot}</div>
              <div className="right">{rightSlot}</div>
            </div>
          );
        }
      }
      class Main extends React.Component {
        render() {
          return (
            <NavBar
              leftSlot={<div>我是左边内容</div>}
              rightSlot={<div>我是右边内容</div>}
              centerSlot={<div>我是中间内容</div>}
            />
          );
        }
      }
      ReactDOM.render(<Main />, document.getElementById("root"));
    </script>
  </body>
</html>

受控组件和非受控组件

受控组件本质上就是表单组件,在react中表单数据交由state管理。这是react自己创造的一个概念。
非受控组件在react中也是指表单组件,只是数据不是state控制的,用户提供直接操作dom来管理表单数据。几乎是不会用的,只是相当于受控组件的一个概念。

受控组件

下面的代码有一个非常重要的特点是,在设置了value的值后,浏览器的输入框是不能再输入内容的,也不能修改内容。这是被react作用到的。正常的html设置了input的value内容还是可以编辑的。

还有一个小细节是label的绑定id的属性是htmlFor,原来是for,这是因为for是关键字。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        render() {
          return (
            <div>
              <label htmlFor="name">
                姓名:
                <input id="name" value="Tom" />
              </label>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

在这里插入图片描述
给input添加onChange事件,这就是受控组件了。我们在state中绑定了value值,这就是react的受控组件的概念了。实际就是给input组件实现了数据绑定。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          username: "Tom",
        };
        handleChange(e) {
          console.log(e.target.value);
          this.setState({
            username: e.target.value,
          });
        }
        render() {
          return (
            <div>
              <label htmlFor="name">
                姓名:
                <input
                  id="name"
                  value={this.state.username}
                  onChange={(e) => this.handleChange(e)}
                />
              </label>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

表单提交行为和受控组件

下面的代码就是传统表单的提交方式。 这种方式现在已经没人用了。下面的代码在提交表单的时候会刷新页面。

              <form action="/adduser">
                <label htmlFor="name">
                  姓名:
                  <input
                    id="name"
                    value={this.state.username}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <button type="submit">提交</button>
              </form>

react的表单提交方式:
下面的代码先是取消了action属性。通过拦截onSubmit方法来实现表单的提交。核心的逻辑就是handleSubmit方法。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          username: "Tom",
        };
        handleSubmit(event) {
          console.log(event);
          //1.阻止默认行为
          event.preventDefault();
          //2.获取控件数据内容
          console.log(this.state.username);

          //3.通过axios提交给服务器
        }
        handleChange(event) {
          console.log(event.target.value);
          this.setState({
            username: event.target.value,
          });
        }
        render() {
          return (
            <div>
              <form onSubmit={(e) => this.handleSubmit(e)}>
                <label htmlFor="name">
                  姓名:
                  <input
                    id="name"
                    value={this.state.username}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <button type="submit">提交</button>
              </form>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

多个受控组件使用同一个函数处理

其实就是一个处理技巧。通过event.target.name来动态获取属性名,当然,需要我们指定元素name属性的值。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          username: "Tom",
          password: "123",
        };
        handleSubmit(event) {
          console.log(event);
          //1.阻止默认行为
          event.preventDefault();
          //2.获取控件数据内容
          console.log(this.state.username, this.state.password);

          //3.通过axios提交给服务器
        }
        handleChange(event) {
          console.log(event.target.name);
          this.setState({
            [event.target.name]: event.target.value,
          });
        }
        render() {
          return (
            <div>
              <form onSubmit={(e) => this.handleSubmit(e)}>
                <label htmlFor="name">
                  姓名:
                  <input
                    id="name"
                    name="username"
                    value={this.state.username}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <label htmlFor="name">
                  密码:
                  <input
                    id="password"
                    type="password"
                    name="password"
                    value={this.state.password}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <button type="submit">提交</button>
              </form>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

单个checkbox和多个checkbox

单个checkbox

我们实现一个登录页面的代码,有用户名密码和同意协议。需要注意的是,checkbox使用的属性是checked不是value。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          username: "Tom",
          password: "123",
          isAgree: false,
        };
        handleSubmit(event) {
          console.log(event);
          //1.阻止默认行为
          event.preventDefault();
          //2.获取控件数据内容
          const { username, password, isAgree } = this.state;
          console.log(username, password, isAgree);
          //3.通过axios提交给服务器
        }
        handleChange(event) {
          console.log(event.target.name);
          this.setState({
            [event.target.name]: event.target.value,
          });
        }
        handleCheckedChange(event) {
          this.setState({
            isAgree: event.target.checked,
          });
        }
        render() {
          return (
            <div>
              <form onSubmit={(e) => this.handleSubmit(e)}>
                <label htmlFor="name">
                  姓名:
                  <input
                    id="name"
                    name="username"
                    value={this.state.username}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <br />
                <label htmlFor="name">
                  密码:
                  <input
                    id="password"
                    type="password"
                    name="password"
                    value={this.state.password}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <br />
                <label htmlFor="isAgree">
                  <input
                    id="isAgree"
                    type="checkbox"
                    checked={this.state.isAgree}
                    onChange={(e) => this.handleCheckedChange(e)}
                  />
                  用户协议
                </label>
                <button type="submit">提交</button>
              </form>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>
多个checkbox

多个checkbox主要也是表单的一些选项,这是非常常见的。需要注意的是。我们需要定义一个数组来管理多个checkbox的状态。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      class MyComponent extends React.Component {
        state = {
          username: "Tom",
          password: "123",
          isAgree: false,
          hobbies: [
            { name: "唱", value: "sing", checked: false },
            { name: "跳", value: "dance", checked: false },
            { name: "rap", value: "rap", checked: false },
          ],
        };
        handleSubmit(event) {
          console.log(event);
          //1.阻止默认行为
          event.preventDefault();
          //2.获取控件数据内容
          const { username, password, isAgree, hobbies } = this.state;
          const selectedHobbies = hobbies
            .filter((item) => item.checked)
            .map((item) => item.value);
          console.log(username, password, isAgree);
          console.log(selectedHobbies);
          //3.通过axios提交给服务器
        }
        handleChange(event) {
          console.log(event.target.name);
          this.setState({
            [event.target.name]: event.target.value,
          });
        }
        handleCheckedChange(event) {
          this.setState({
            isAgree: event.target.checked,
          });
        }
        handleHobbiesChange(event, index) {
          const hobbies = [...this.state.hobbies];
          hobbies[index].checked = event.target.checked;
          this.setState({
            hobbies,
          });
        }
        render() {
          const { username, password, isAgree, hobbies } = this.state;

          return (
            <div>
              <form onSubmit={(e) => this.handleSubmit(e)}>
                <label htmlFor="name">
                  姓名:
                  <input
                    id="name"
                    name="username"
                    value={username}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <br />
                <label htmlFor="password">
                  密码:
                  <input
                    id="password"
                    type="password"
                    name="password"
                    value={password}
                    onChange={(e) => this.handleChange(e)}
                  />
                </label>
                <br />
                {hobbies.map((item, index) => {
                  return (
                    <label htmlFor={item.value} key={index}>
                      <input
                        id={item.value}
                        type="checkbox"
                        checked={item.checked}
                        onChange={(e) => this.handleHobbiesChange(e, index)}
                      />
                      {item.name}
                    </label>
                  );
                })}
                <br />
                <label htmlFor="isAgree">
                  <input
                    id="isAgree"
                    type="checkbox"
                    checked={isAgree}
                    onChange={(e) => this.handleCheckedChange(e)}
                  />
                  用户协议
                </label>
                <br />

                <button type="submit">提交</button>
              </form>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

在这里插入图片描述

高阶函数和高阶组件

高阶函数回顾

什么是高阶函数。一个函数接收一个函数作为参数或者返回一个函数,那个这个函数就是高阶函数。

高阶组件(High Order-Component HOC)

这是react官方给的定义。
官方定义: 高阶组件是参数为组件,返回值为新组件的函数。
注意: 虽然叫高阶组件,但实际上是函数。
接收一个组件作为他的参数。

高阶组件简单例子

高阶组件的主要意义就是对传入的组件进行拦截,这样,我们就可以对传入的组件做一些额外操作了。
下面的代码就是一个简单的高阶组件,虽然没有什么实际意义。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      //   import { PureComponent } from "react";
      class FooComponent extends React.PureComponent {
        render() {
          return <div></div>;
        }
      }

      function enhanceComponent(OldComponent) {
        class NewComponent {
          render() {
            return <OldComponent name="commonName" />;
          }
        }
        return NewComponent;
      }

      ReactDOM.render(<FooComponent />, document.getElementById("root"));

      const newComponent = enhanceComponent(FooComponent);
      console.log(newComponent);
    </script>
  </body>

高阶组件的应用场景一:props

下面的代码可以给每个组件注入一个userInfo到各自的props里面。我们需要把自身的props也传进去。

还是有点用的,如果你需要给某些组件插入一些测试数据的话。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      //   import { PureComponent } from "react";
      class MyComponent extends React.PureComponent {
        render() {
          return (
            <div>
              <Home />
              <Profile />
              <Find />
            </div>
          );
        }
      }

      const Home = enhanceComponent(function (props) {
        return <div>Home {props.name}</div>;
      });
      const Profile = enhanceComponent(function (props) {
        return <div>Profile {props.name}</div>;
      });
      const Find = enhanceComponent(function Find(props) {
        return <div>Find {props.name}</div>;
      });

      function enhanceComponent(OldComponent) {
        class NewComponent extends React.PureComponent {
          state = {
            userInfo: {
              name: "Tom",
              age: 10,
            },
          };
          render() {
            return <OldComponent {...this.props} {...this.state.userInfo} />;
          }
        }
        return NewComponent;
      }

      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

高阶组件的应用场景二:真实应用场景 Context

我们在使用Context的时候,需要写类似下面的代码,这段代码看起来非常的繁琐,而且每次都需要这样写非常的恶心。通过高阶函数可以转变成即简单又优雅的实现方式。

  <body>
    <div id="root"></div>
    <script type="text/babel">
      const ThemeContext = React.createContext();
      class MyComponent extends React.PureComponent {
        render() {
          return (
            <div>
              <ThemeContext.Provider value={{ color: "red", size: 30 }}>
                <Product />
              </ThemeContext.Provider>
            </div>
          );
        }
      }
      class Product extends React.PureComponent {
        render() {
          return (
            <div>
              <ThemeContext.Consumer>
                {(value) => {
                  return <div>{value.size}</div>;
                }}
              </ThemeContext.Consumer>
            </div>
          );
        }
      }
      ReactDOM.render(<MyComponent />, document.getElementById("root"));
    </script>
  </body>

http://www.kler.cn/a/404889.html

相关文章:

  • 多算法模型(BI-LSTM GRU Mamba ekan xgboost)实现功率预测
  • XLNet——打破 BERT 局限的预训练语言模型
  • 内存(RAM)详细介绍
  • js批量输入地址获取经纬度
  • [大数据]Trino
  • 【Linux】<共享内存应用>——模拟实现不同进程把hello字符对<共享内存文件对象>的放入和取出操作
  • P8692 [蓝桥杯 2019 国 C] 数正方形:结论,组合数学
  • 使用ENSP实现静态路由
  • CPU详细介绍
  • Grafana监控PostgreSQL
  • 机器学习系列----关联分析
  • 京东最新黑边背景旋转验证码识别
  • ApiChain 从迭代到项目 接口调试到文档生成单元测试一体化工具
  • Docker+fastapi
  • 2.预备知识
  • SentenceTransformers×Milvus:如何进行向量相似性搜索
  • SAP PI/PO Proxy2JDBC SQL_QUERY动态接口示例
  • H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
  • 视频流媒体播放器EasyPlayer.js无插件直播流媒体音视频播放器Android端webview全屏调用无效问题
  • Hello-Go
  • 腾讯云单元化架构体系介绍
  • 深入探索Solana链上的Meme生态:创新、潜力与挑战#区块链开发#dapp开发
  • xml和xpath
  • C# BitmapSource小节
  • TensorFlow如何调用GPU?
  • 【Linux学习】【Ubuntu入门】1-7 ubuntu下磁盘管理