掘金 阅读 ( ) • 2024-04-02 11:14

theme: channing-cyan

react学习前置准备

第一步安装vite

在项目中初始化vite

在命令行中全局安装vite

npm i -g vite
npm create vite

在vite中选择react的基础配置

根据提示选择对应的框架即可

在跳转到建立的项目文件夹中

cd ***(创建的项目文件夹的名字)

配置vite的依赖包

npm i

执行对应的vite命令即可

npm dev

React的基本知识

(在vite工具的帮助下,不需要注意这些)

基本的React了解

名称 介绍 babel.min.js 主要将Jsx转换成js格式 react.development.js React核心库 React-Dom.delelopment.js 用于支持React操作Dom

引入的顺序

  1. 先引入核心库react.development.js
  2. 在引入react-dom,用于支持react操作dom
  3. 最后引入babel,用于将Jsx转换成js,让后创建dom
  4. 如果使用vite等打包脚手架工具,则不需要以上步骤

写react的准备

  1. 写react代码要注明

    <script type="text/babel">
    

    在末尾引入,这样浏览器才知道写的是Jsx的代码

  2. 控制台上favicon.ico 的警告提醒,在项目文件的根目录中放一张ico偏爱图标即可,图标名字一定要为favicon.ico,不然浏览器查询不到

了解react的简单使用方法

开始准备容器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    //引用react
    <script type="text/javascript" scr="../js/react.development.js"></script>
    <script type="text/javascript" scr="../js/react-dom.development.js"></script>
    <script type="text/javascript" scr="../js/babel.min.js"></script>
    <title>Document</title>
</head>
<body>
    <div id="test"></div>
    //准备容器,用于放置react渲染的dom
</body>
</html>

创建虚拟Dom,并渲染到界面上

<script type="text/babel">
    //创建虚拟dom
    const vm=<h1>hello,react</h1>
    
    //也可以写成括号包裹形式,便于观看,如下
    const vm=(
    	<h1>hello,react</h1>
    )
    
    
     //通过js创建虚拟dom,这样不用引入babel.min.js
    const vm= react.createElement('h1',{id:'test'},'hello,react')
    
    
    //渲染虚拟dom到页面
    ReactDom.render(vm,document.getElentById"test")
</script>

jsx最终在浏览器中呈现的格式都是js模式,如上方格式,不过常用的是其语法糖模式

如果用js创建虚拟dom,第一部分写标签名,第二部分写标签属性,以对象的形式写,可在内写多个属性,第三部分写标签内容

标签内容中可嵌套

const vm= react.createElement('h1',{id:'test'},react.createElement('span',{id:test1},'你好'))

关于虚拟DOM

  1. 本质是object类型的对象

  2. 因为虚拟dom在react的内部使用,不需要太多的属性,所以虚拟dom的属性较少,所以比较"轻",速度更快

  3. 虚拟DOM最终会被react转换成真实DOM,呈现在页面

  4. jsx是resct定义的一种类xml的js扩展语法

    xml语法

<student>
<name>tom</name>
<age>18</age>
</student>

xml语法现在基本被json语法替代

{
 'name':'tom',
 'age':'18'
}

jsx的语法规则

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

  2. 标签中混入js表达式时要用{}号包裹

  3. 样式的类名指定时不要用class,用className,避免与js语法混搅

  4. jsx中css的内联样式要用style={{key:value}}的双括号形式去写,原本js中要引用css内联样式应写style="key:value"

  5. 虚拟标签dom只用一个根标签,一个根标签对应一个虚拟DOM,如果有多个同级虚拟DOM,要用一个标签将其包裹

  6. 标签必须闭合,所以像input这种单标签,要写成以下形式

    <input type='text'/>
    
  7. 标签首字母,如果是小写字母,则将标签转成html的同名标签元素;如果是大写字母开头,则将其渲染成react组件

小例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    //引用react
    <script type="text/javascript" scr="../js/react.development.js"></script>
    <script type="text/javascript" scr="../js/react-dom.development.js"></script>
    <script type="text/javascript" scr="../js/babel.min.js"></script>
    <title>Document</title>
</head>
<body>
    <div id="test"></div>
    //准备容器,用于放置react渲染的dom
</body>
        
        
        
       <script type="text/babel">
           const myId='zs'
           const myData='hello'
           //创建虚拟dom
           const vm=(
           <div>
               //假设title为假设外联式的css
              <h2 className='title' id={myId.tolowerCare()}>
                  <span style={{color:'white',font:'29px'}}>{myData.tolowerCare()}</span>
              </h2>
               
              <input type='text'/>
           </div>
    		)
        </script>
</html>

简单理解:

  • 表示固定的字符串就用正常的 ' ' 和 " " 的引号,如 className='title'
  • css内联式用{{ }}号
  • 自定义的js表达式就用{ }号
  • 类名用className
  • 原因解析:原js语法中标签都是' '形式,都是字符串,没有变量的概念;所以jsx语法中用js表达式(本质上是变量赋值)时用的是{ }号;而css内联式是键值对的形式,便不可以用单括号,便用{{ }}双括号来表示

小例子 动态将数组中的名称建立到列表标签中

 <script type="text/babel">
     const data=['zs','ls','ww']
     const vm=(
     <div>
         <ul>
             {data.map((item,index)=>{
                 return <li key={index}>{item}</li>
                 //react为了diff算法,其中的标签中都要赋予唯一的key值
                 //例子中的key值赋予的有问题,在进行数组拼接时会出现key值重复
             }
             )}
         </ul>
     </div>	)
  </script>

函数式组件

<script type="text/babel">
   function Dome(){
        return <h2>函数式组件</h2>
    }
    
    //渲染虚拟dom到页面
    ReactDom.render(<Dome/>,document.getElentById"test")
</script>

该自定义函数this本应指向window,但是经过babel翻译后,开启了严格模式,进制来this指向window指向window,所以查看时,该this为undefined

严格模式开启 "use strict"

  1. react解析组件标签,找到Component组件
  2. 发现是组件是函数定义,随后调用该函数,将虚拟Dom转为阵势dom
  3. 呈现在页面中

函数式组件的props

props

传递过来的属性是只读的,如果想修改可以弄一个值接受一下,但是不能直接操作props

Object.freeze(obj)冻结,不能修改,不能增删,不能劫持

Object.seal(obj)密封,能修改,不能增删,不能劫持

Object.preventExtensions(obj)不可扩展,能修改,不能新增

import React from "react";
import PropTypes from 'prop-types';

  Person.propTypes={
    name:PropTypes.string.isRequired,
    sex:PropTypes.string,
    age:PropTypes.number
  }
//对函数组件的props做限制,因为不是类,所以不能用类的静态方法,只能使用函数Person中方法
//如果使用ts,该插件可以不再使用
  Person.defaultProps = {
    sex: "男",
    age: 18,
  };
//与上同理

function Person(props){
    //创建函数并接受props
    const {name,sex,age}=props
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
        <li>年龄:{age + 1}</li>
      </ul>
    );
  }

export default Person;


props.children

子组件中如果不存在内容,props.children,就为null

子组件中如果存在一个标签,props.children,就为该标签的对象,可以在大胡子语法中直接渲染

props一般是用来传值,而props.children 是 React 中的一个特殊属性。它允许你在组件标签内传递子元素。例如:

<MyComponent>
  <p>Hello, world!</p>
</MyComponent>

MyComponent 组件内部,可以通过 props.children 属性来访问子元素 <p>Hello, world!</p>。这样,MyComponent 组件就会将子元素 <p>Hello, world!</p> 显示在自己的内容区域中。

function MyComponent(props) {
  return (
    <div>
      {props.children}
    </div>
  );
}

子组件中如果存在多个标签,props.children,就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置

<MyComponent>
   <p>Hello, world!</p>
   <p>你好,世界</p>
</MyComponent>
function MyComponent(props) {
  return (
    <div>
      {props.children[1]}//你好,世界
      {props.children[0]}//Hello, world!
    </div>
  );
}

子组件中存在多个标签,还可以给组件子元素加上一个slot='???'的属性作为名字,props.children,就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置

    <>
      <Hello>
        <p slot="0">123</p>
        <p slot="1">234</p>
      </Hello>
    </>
 <>
 <div>
 {props.children.map((item) => {
     if(item.props.slot==="1"){
     //注意item.props,是props.children.props不是props
         return item
     }
 })}
 
  {props.children.map((item) => {
     if(item.props.slot==="0"){
     //注意item.props,是props.children.props不是props
         return item
     }
 })}



 //这么写也行 
   <>
   <div>
   {props.children.map((item) => {
      return item.props.slot==="1"&&item
   })}
   </div>
   </>
   
 </div>
 </>

子组件被多次调用,还可以给组件加上一个slots='???'的属性作为组件名字,props.children就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置 一般是弄个变量在上面接收好,再在下面使用,避免下面的代码太长

    <>
      <Hello>
        <p slot="0">123</p>
        <p slot="1">234</p>
      </Hello>
      <Hello slots="3">
        <p >456</p>
      </Hello>
    </>
 <>
 <div>
 {
     props.slots=='3'&&props.children
 }
 {
     props.slots==null&&props.children
 }
 </div>
 </>

props.render

在React中,一个组件的props是通过props属性传递给它的,可以使用props来获取组件传递的数据。而render函数是一种用来创建组件的方法,它可以将组件渲染到页面上。可以将render函数作为组件的一个属性传递给组件,并在组件的render方法中调用该属性。这样,可以实现动态生成DOM元素或者执行一些逻辑的功能。

例如,可以定义一个MyComponent组件,并将一个带有render函数的props传递给它,如下所示:

<MyComponent render={() => <span>Hello World</span>} />
function MyComponent(props) {
  return <div>{props.render()}</div>;
}

在上面的例子中,MyComponent组件接收一个名为render的props,它是一个函数,返回一个带有Hello World文本的span元素。在MyComponent组件的render方法中,调用了props.render函数,并将其返回的元素渲染到了页面上。

通过这种方式,可以在组件内部动态生成DOM元素,并且可以根据需要实现更为复杂的功能。

两者的区别

props.render是一个函数,它接收一些参数并返回一个组件。通常用于在父组件中动态地渲染子组件。例如:

function Parent(props) {
  return (
    <div>
      {props.render()}
    </div>
  )
}

function Child() {
  return (
    <h1>Hello, world!</h1>
  )
}

function App() {
  return (
  //在标签内属性定义一个回调
    <Parent render={() => <Child />} />
  )
}

props.children表示组件的子元素。它可以是一个组件,也可以是多个组件。React会将children作为props传递给父组件,从而允许我们在组件内部使用组件。例如:

function Parent(props) {
  return (
    <div>
      {props.children}
    </div>
  )
}

function Child1() {
  return (
    <h1>Hello, world!</h1>
  )
}

function Child2() {
  return (
    <p>This is a paragraph</p>
  )
}

function App() {
  return (
  //在标签内容定义组件
    <Parent>
      <Child1 />
      <Child2 />
    </Parent>
  )
}

因此,props.render用于动态地渲染子组件,而props.children用于将已经存在的组件作为父组件的子元素。


函数式组件的useState

useState的使用

import React from 'react'
function Index () {

//类式组件改变状态的样式,和下面函数式组件对比学习  
// state={count:0}
    
//add=() => {
   //this.setState({count:count+1})
//}
  
    
//对状态的数组进行解构赋值,数组第一个参数是状态名;第二个参数是改变状态的函数。相当于setState
const [count,setCount]=React.useState(0)
const [name,setName]=React.useState('jack')

function add() {
   //在事件回调的函数中触发对应状态的函数
  setCount((count) => {
      return count+1}
      )
}

function changeName() {
    //像这种简单的改变,直接向set***中传入参数即可
  setName('tom')
}
   
    return (
      <div>
        <h1>当前求和为{count}</h1>
        <h1>{name}</h1>
        <button onClick={add}>点击加一</button>
        <button onClick={changeName}>点击换名</button>

      </div>
    )
  

}
export default Index

闭包和消息队列的骚扰

import { useState } from "react";
function Hello() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("jack");

  // 函数式组件每次渲染,都是函数的重新创建,所以就产生了闭包问题
  //useState,只有第一次设的初始值会生效,后续的值都是setState函数的返回值
  //也是因为useState只有第一次会执行,所以像对初始数据处理的行为,可以直接在useState里写个回调处理
  //重新创建的组件函数拿到setState函数的返回值,实现界面上数值的更新
  //因为新值是新创建的组件函数拿到的,所以在这里不经过处理拿到的都是没修改前的旧值



  function add0() {
      console.log("因为闭包只能显示旧值,会比界面上现实的值小一:",count);
      setCount(count + 1); 
  }
  
  
   
  
  
  
  function add1() {
    for (let i = 0; i < 10; i++) {
      setCount(count + 1);
    }
  }
  //结果并不会加十,因为这里每次只能读到旧值
  //所以虽然执行了10次,但是每次读取都是0,进行了十次0+1,结果就为1
  
  
  
  
  
  

  function add2() {
    for (let i = 0; i < 10; i++) {
      setCount((count) => {
        return count + 1;
      });
    }
  }
 //结果会加十,因为这里每里用了回调函数实现了闭包,使值在回调函数中被记录了下来
 //所以结果就为11



  function changeName() {
    setName("tom");
  }

  return (
    <div>
      <h1>当前求和为{count}</h1>
      <h1>{name}</h1>
      <button onClick={add0}>闭包,点击加一</button>

      <button onClick={add1}>错误的点击加十</button>
      <button onClick={add2}>正确的点击加十</button>
      <button onClick={changeName}>点击换名</button>
    </div>
  );
}

export default Hello;

结论

state简单的修改就用直接用

涉及到对值重复修改就要考虑闭包问题,最好使用回调函数进行闭包记值

setStata修改出现的问题

import { useState } from "react";
function Hello() {
  const [count, setCount] = useState({
    boyNum: 100,
    girlNum: 100,
  });
  function addBoy() {
      setCount({
          ...count,
          //setState,每次都是将值重新赋值给新渲染的组件函数,所以不能单独渲染某一个值
          //要将原本的值全部展开,再修改某一个值,不然那些值就会丢失
          //如果一开始就想只修改特定的值,建议直接那些值拆开,不要放在一个对象
          boyNum: count.boyNum + 1
      })
  }
  function addGirl() {
    setCount({
        ...count,
        girlNum: count.girlNum + 1
    })
  }

  return (
    <div>
      <h2>当前人数为{count.boyNum + count.girlNum}</h2>
      <h3>男生人数为{count.boyNum}</h3>
      <h3>女生人数为{count.girlNum}</h3>
      
      <button onClick={addBoy}>男生加1</button>
      <button onClick={addGirl}>女生加1</button>
     
    </div>
  );
}

export default Hello;