likes
comments
collection
share

React基础知识 --个人学习笔记个人学习笔记,记录学习使用,别点11111111111111111111111111

作者站长头像
站长
· 阅读数 20

react学习前置准备

常用到的网站

学习网站网址种类
React中文React中文文档文档
React RouterReact Router6 中文文档 路由
React RouterReact Router路由
zustandzustand状态管理
TanStack QueryTanStack Query 中文文档请求管理,路由
reduxRTK状态管理,请求管理
FetchFetch - MDN请求
fontawesome图标fontawesome图标图标
AntD Ant Design图标
Next.jsNext.js类似框架
solidjssolidjs类似框架
生态圈awesome-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.jsReact核心库
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>

代码判断执行的小技巧

回调函数存在即执行,没有就忽略

cb && cb();

Json 存在参数就放进去,不存在就忽略

body:body?JSON.stringify({data:body}):null,

函数式组件

<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. 呈现在页面中

React事件处理

react中的事件处理的两个重点

通过onXxx属性指定处理函数,驼峰式命名

a.React中的事件都是重新封装过的。不是js中的原生DOM事件-----------为了兼容性

b.React中的事件都是通过事件委托到组件最外层的元素的-----------为了高效

通过event.target获得事件

通过event.target得到发生事件的DOM对象 避免过渡使用ref

import React, { useRef, useCallback } from "react";  
  
const Person = () => {  
  // 使用 useRef 创建 ref  
  const myRef = useRef(null);  
  
  // 使用 useCallback 创建事件处理函数  
  const showData = useCallback(() => {  
    if (myRef.current) {  
      alert(myRef.current.value);  
    }  
  }, [myRef]);  
  
  const showData2 = useCallback((event) => {  
    alert(event.target.value);  
     //通过event.target得到发生事件的DOM对象
    //因为该组件的数据,点击事件都是在自身上,所以事件通过event.target得到发生事件的DOM对象上的数据即可,可以不用ref
  }, []);  
  
  return (  
    <div>  
      <input type="text" ref={myRef} placeholder="点击显示数据" />  
      <button onClick={showData}>点击上方显示数据</button>  
       //通过onXxx属性指定处理函数,驼峰式命名
      <br />  
      <input type="text" onBlur={showData2} placeholder="失去焦点显示数据" />  
      <br />  
    </div>  
  );  
};  
  
export default Person;

受控组件和非受控组件

在React中,受控组件和非受控组件是常见的两种组件形式。它们的区别在于组件是否自己管理自己的状态。

受控组件

  • 受控组件的状态(例如输入框中的值)由React组件的state管理。
  • 当用户在受控组件中输入内容时,React组件的state会更新,因此文字也会相应地更新。
  • 开发人员可以在onChange事件中更新状态,以响应用户输入。

非受控组件

  • 非受控组件的状态由DOM管理,而不是由React组件的state管理。
  • 当用户在非受控组件中输入内容时,DOM自己管理了输入值。
  • 开发人员可以使用ref(引用)来访问组件的值。

总结

  • 当开发人员需要访问和控制组件中的值时,应该使用受控组件。
  • 当用户输入的值不需要在组件上进行操作时,可以使用非受控组件。
import React, { useState, useRef } from "react";  
  
const Person = () => {  
  // 创建refs用于非受控组件通过操控DOM的形式更新值  
  const myRef = useRef(null);  
  const myRef1 = useRef(null);  
  
  // 受控组件的状态变量  
  const [username, setUsername] = useState('');  
  const [password, setPassword] = useState('');  
  
  // 非受控组件处理表单提交  
  const handleSubmit = (event) => {  
    event.preventDefault();  
    // 使用refs获取输入框的值  
    const usernameValue = myRef.current.value;  
    const passwordValue = myRef1.current.value;  
    confirm(`你输入的账号是${usernameValue},你输入的密码是${passwordValue}`);  
  };  
  
  // 受控组件保存用户名  
  const saveUsername = (event) => {  
    setUsername(event.target.value);  
  };  
  
  // 受控组件保存密码  
  const savePassword = (event) => {  
    setPassword(event.target.value);  
  };  
  
  return (  
    <form action="" onSubmit={handleSubmit}>  
      用户:  
      {/* 受控组件通过setState的形式控制输入和更新值 */}  
      <input type="text" name="username" onChange={saveUsername} value={username} /><br />  
      密码:  
      {/* 受控组件通过setState的形式控制输入和更新值 */}  
      <input type="text" name="password" onChange={savePassword} value={password} /><br />  
      <button>登录</button>  
    </form>  
  );  
};  
  
export default Person;

高阶函数的柯西里化

对函数代码的柯里化优化

import React, { useState, useRef } from "react";  
  
const Person = () => {  
 const [user, setUser] = useState({
    username: '',
    password: '',
  })
  const myRef = useRef();  
  const myRef1 = useRef();  
  
  const handleSubmit = (event) => {  
    event.preventDefault();  
    const usernameValue = myRef.current.value;  
    const passwordValue = myRef1.current.value;  
    confirm(`你输入的账号是${usernameValue},你输入的密码是${passwordValue}`);  
  };  
  
    
       
  
  const saveForm = (dataType) => {
   // 用参数传递类型,从而区分函数的作用  
    // 让该函数的返回值返回所需要的函数  
  return (event) => {  
      setUser({
      ...user,
      [dataType]: event.target.value
       //方括号可以在对象字面量中动态的解析key
       //不用方括号就会出现,dataType:123
  
      }
      );  
   
    }  
  };  
  
  return (  
    <form action="" onSubmit={handleSubmit}>  
      用户:  
      <input  
        type="text"  
        name="username"  
        onChange={saveForm('username')}  
        value={user.username}  
        ref={myRef}  
      />  
      <br />  
      {/* 如果是onChange={saveForm},则就是直接调用saveForm函数。 */}  
      {/* 如果是onChange={saveForm('username')},则就是直接调用saveForm函数的返回值,即一个处理用户名更新的函数。 */}  
  
      密码:  
      <input  
        type="text"  
        name="password"  
        onChange={saveForm('password')}  
        value={user.password}  
        ref={myRef1}  
      />  
      <br />  
      <button>登录</button>  
    </form>  
  );  
};  
  
export default Person;



高阶函数定义

1.接收的参数是一个函数

2.被调用的返回值是一个参数

对函数代码的非柯里化优化

import React, { useState, useRef } from "react";  
  
const Person = () => {  
  const [username, setUsername] = useState("");  
  const [password, setPassword] = useState("");  
  
  const myRef = useRef();  
  const myRef1 = useRef();  
  
  const handleSubmit = (event) => {  
    event.preventDefault();  
    const usernameValue = myRef.current.value;  
    const passwordValue = myRef1.current.value;  
    confirm(`你输入的账号是${usernameValue},你输入的密码是${passwordValue}`);  
  };  
  
  const saveForm = (dataType, event) => {  
    // 通过参数传递类型,从而区分函数的作用  
    // 在事件处理函数中更新对应的状态  
    const { value } = event.target;  
    if (dataType === 'username') {  
      setUsername(value);  
    } else if (dataType === 'password') {  
      setPassword(value);  
    }  
  };  
  
  return (  
    <form action="" onSubmit={handleSubmit}>  
      用户:  
      <input  
        type="text"  
        name="username"  
        onChange={(event) => saveForm('username', event)}  
        value={username}  
        ref={myRef}  
      />  
      <br />  
     //和上面的柯里化差不多的道理,通过调用回调函数,在回调函数中再去调用别的函数  
       
      密码:  
      <input  
        type="text"  
        name="password"  
        onChange={(event) => saveForm('password', event)}  
        value={password}  
        ref={myRef1}  
      />  
      <br />  
      <button type="submit">登录</button>  
    </form>  
  );  
};  
  
export default Person;

函数式组件的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元素,并且可以根据需要实现更为复杂的功能。

示例

1.父组件Dome将render={() => {return <A>}}作为Props

2.传给子组件B,<B render={() => {return <A>}}/>

3.子组件B中,接收作为返回值返回的孙组件A,() => {return <A>}

import {  useState,useContext} from "react";
import { createContext } from 'react';

export const MyContext = createContext({ 
  Name:'jack', 
  age:20
 });






function Hello() {

  const [Name, setName] = useState('jack');  
  const [age, setAge] = useState(18);  
  
  // useEffect 可以用来模拟类组件的某些生命周期方法,但在这个例子里我们不需要它  
  
  // change 函数用于切换名字  
  const change = () => {  
    setName(prevName => (prevName === 'jack' ? 'Tom' : 'jack'));  
  };  
  
  // 渲染函数  
  const renderA = () => <A />;  
  
  // 渲染逻辑  
  console.log('Dome 组件渲染了');  
  return (  
    <div className="parent" style={{ width: '500px', height: '90vh', background: 'gray',margin: '10px',padding: '10px',}}>  
      <h3>我是 hello 组件</h3>  
      <h4>我的名字是:{Name}</h4>  
      <button onClick={change}>点击换名</button>  
  
      <MyContext.Provider value={{ Name, age }}>  
        {/*   
          重点部分:通过 render 属性传递一个函数,该函数返回 <A/> 组件  
          B 组件里提前留好 {this.props.render()} 插槽  
          render={() => {return <A/>}} 里的 <A/> 就是插件  
          这样就可以在设置好之后,不经过 B 组件,就可以通过插槽,在 Dome 组件里修改 B 组件中的子组件  
        */}  
        <B Name={'固定的jack'} render={renderA} />  
      </MyContext.Provider>  
    </div>  
  );  
};  


const B = ({ Name, render }) => {  
    console.log('B组件渲染了');  
    return (  
        <div className="child" style={{ width: '400px', height: '50vh', background: 'skyblue', margin: '10px', padding: '10px',}}>  
            <h3>我是B组件</h3>  
            <h4>我的名字是{Name}</h4>  
            {/* B组件里提前留好插槽 */}  
            {render()}  
        </div>  
    );  
};  
  
 
const A = () => {  
    console.log('A组件渲染了');  
    const { Name, age } = useContext(MyContext);  
      
    // 假设 AA 组件已经定义好了,并且可以在这里直接使用  
    // 如果 AA 组件也需要使用上下文或者具有其他状态,那么它也应该被重写为函数式组件  
    return (  
        <div className="grand" style={{ width: '300px', height: '200px', background: 'orange' ,margin: '10px',padding: '10px',}}>  
            <h3>我是A组件</h3>  
            <h4>我爷爷的名字是{Name},年龄{age}</h4>  
            <B Name={Name} render={() => <span>我又在A中插入了</span>} />  
        </div>  
    );  
};  


const AA = () => {

    console.log('AA组件渲染了');
    const {Name,age}=useContext(MyContext)
      
        return (
            <div className='grand'style={{width:'250px',height:'100px',background:'pink'}}>
                <h3>我是AA组件</h3>
                <h4>我爷爷的名字是{Name},年龄{age}</h4>
            </div>
        );
}
 
export default Hello;
 
export default Dome;

两者的区别

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;

函数组件的useEffect

useEffect的用法

讲解 因为函数式组件没有生命周期,所以用useEffect来模仿生命周期

常用于 1.发网络请求 2.设置订阅 3.启动定时器 4.手动更改真实DOM

 React.useEffect(() => {
  //在这里执行副作用操作,如挂载后,更新后的定时器开启,状态改变之类的
   ...
//在这里执行收尾操作,如卸载组件后的清除定时器
    return () => {
      ...
    };
    //[]里是检测对象。如果该数组没写,则是检测所有的生命周期行为;
    //如果有数组但是为空的话,就是只检测挂载行为
    //如果有数组而且为状态的话,将检测该状态的生命周期
  }, []);

示例

import React from "react";
import root from "../main";

function Index() {

  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
  //组件挂载时启动定时器
    let timer = setInterval(() => {

      setCount((count) => {
        return count + 1;
      });

    }, 1000);
//组件删除时,清理定时器
    return () => {
      clearInterval(timer);
    };
    
  }, []);
//点击事件触发的函数
  function unm() {
    root.unmount();
  }

  return (
    <>
      <h1>计时为:{count}</h1>
      <button onClick={unm}>点击卸载</button>
    </>
  );
}
export default Index;

模拟生命周期

  1. 在第一次渲染完毕执行 在组件每次更新后也会执行 所以它可以获得最新的状态值
 useEffect(() => {
 consloe.log("ok")
 });
  1. 加了空数组它就只会在组件第一次渲染完成才会更新
 useEffect(() => {
 consloe.log("ok")
 },[]);
  1. 加了有值数组它在组件第一次渲染完成,和数组值发生变化时都会更新
 useEffect(() => {
 consloe.log("ok")
 },[x,y]);
  1. 加了返回的回调函数,会在effect执行之后再执行
useEffect(() => {
  let timer = setInterval(() => {
    setCount((count) => {
      return count + 1;
    });
  }, 1000);
  return () => {
    clearInterval(timer);
      };   
  }, []);

对useEffect的理解--超级重要

  1. 只有一个回调的useEffect普通函数差不多,在组件函数重新执行时被创建及调用
  2. 只有空数组的useEffect就和useState差不多,在组件函数第一次执行时被调用,后面不再调用
  3. 有数值数组的useEffect就在数值绑定,在setState时被调用
  4. 带返回的effect也是一个道理,在useEffect内容执行完,返回一个结果或者回调,同时组件函数被重新渲染,先执行上一次留些的回调,然后下一个useEffect开始执行
  5. 难怪叫它副作用,,,没理解透,用的不好,后患无穷

useEffect对异步的处理

useEffect中只能接收函数,不能接受promise,所以调用时尽量用then处理

  useEffect(() => {
    async function fetchData() {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts");
      const data = await response.json();
      flushSync(() => {
        setBoy(data[7].id);
      });
    }
    fetchData();
  },[]);
 useEffect(() => {
    function fetchData() {
      fetch("https://jsonplaceholder.typicode.com/posts")
        .then((response) => response.json())
        .then((data) => {
          flushSync(() => {
            setBoy(data[10-1].id);
          });
        });
    };
    fetchData();
  }, []);

UseLayoutEffect

useLayoutEffect 是 React 提供的一个 Hook,用于在 DOM 变更之后同步执行副作用。它与 useEffect类似,但有一些重要的区别。useEffect是在渲染完成之后执行副作用

而 useLayoutEffect则是在旧 DOM 变更完成之后、优先同步执行useLayoutEffect中的返回及内容。这意味着它会在浏览器绘制之前执行,可以阻塞浏览器渲染,因此应该谨慎使用。

useLayoutEffect 的主要作用是在执行完 DOM 变更后立即读取 DOM 布局信息,然后同步更新其他状态或进行其他 DOM 操作。它常用于计算元素的尺寸、位置等,并根据这些信息进行后续操作。

在使用 useLayoutEffect 时,应注意避免执行长时间运行的任务,以免阻塞页面渲染,也要留意其可能引起的性能问题。如果不需要同步执行副作用,可以使用 useEffect 来代替。

使用 useLayoutEffect 的示例代码如下:

import React, { useLayoutEffect, useRef } from 'react';

function MyComponent() {
  const elementRef = useRef(null);

  useLayoutEffect(() => {
    const element = elementRef.current;
    const rect = element.getBoundingClientRect();
    console.log('Element width:', rect.width);
  }, []);

  return <div ref={elementRef}>Hello, World!</div>;
}

在上述示例中,useLayoutEffect 在渲染完成之后同步执行副作用,获取了 <div> 元素的尺寸信息,并将其打印到控制台上。

uselnsertionEffect

useInsertionEffect 是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用 useEffect 或者 useLayoutEffect

useInsertionEffect,它允许开发人员在元素被插入到DOM中时执行一些操作。在React中,元素被插入到DOM中通常是在组件挂载时发生的。该回调函数会在元素被插入到DOM中时被调用。由于useInsertionEffect只能在元素插入到DOM中时执行操作,因此它可能无法满足某些特殊的需求,例如在元素被移除时执行操作。相当生命周期挂载完成

function Component() {
  useInsertionEffect(() => {
    document.querySelector('.app').classList.add('red');
  });
  
  return (
    <div className="app">Hello World!</div>
  );
}
import { useInsertionEffect } from 'react-insertion-effect';
 
function MyComponent() {
  useInsertionEffect(() => {
    const handleScroll = () => {
      const scrollPosition = window.innerHeight + window.scrollY;
      const documentHeight = document.body.offsetHeight;
      if (scrollPosition >= documentHeight) {
        // 加载更多内容
      }
    };
 
    window.addEventListener('scroll', handleScroll);
 
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  });
  
  return (
    // 组件内容
  );

组件中使用了useInsertionEffect,会在挂载完成时执行,在回调函数中,我们定义了一个handleScroll函数,用于检查是否需要加载更多内容。我们还添加了一个事件监听器,当用户滚动页面时,handleScroll函数就会被调用。最后在回调函数的返回值中移除了事件监听器,以避免内存泄漏。

函数组件的ref

useRef

import React from "react";

function Index() {

  const [count, setCount] = React.useState(0);

  const myRef=React.useRef()

  function show() {
  confirm('输出:'+myRef.current.value)
  myRef.current.value=''
  }

  return (
    <>
      <input type="text" ref={myRef}/><br />
      <button onClick={show}>点击显示</button>
    </>
  );
}
export default Index;

forwardRef

可以用来指定组件向外部暴露的 ref

useRef() 无法直接去获取 react 组件的 dom 对象, 因为一个 react 组件中可能含有多个 dom 对象 React 也不知道要给你谁,可以用 React.forwardRef() 可以用来指定组件向外部暴露的 ref

在 React 中可以通过 forwardRef 来指定要暴露给外部组件的 ref:

const MyButton = forwardRef((props, ref) => {
    return <button ref={ref}>自定义按钮</button>
});

上例中,MyButton 组件将 button 的 ref 作为组件的 ref 向外部暴露,其他组件在使用 MyButton 时,就可以通过 ref 属性访问:

<MyButton ref={btnRef}/>

可以把 整个 DOM 对象 暴露出来,缺点是不可控,希望可以只暴露操作方法,受限制去操作

const ChildComponent = React.forwardRef((props, ref) => {
  const handleClick = () => {
    // 子组件内部的一些逻辑
  }
  
  return (
    <button ref={ref} onClick={handleClick}>Click Me</button>
  )
})
const ParentComponent = () => {
  const childRef = useRef()

  const handleButtonClick = () => {
    childRef.current.click()
  }

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleButtonClick}>Click Child Button</button>
    </div>
  )
}

useImperativeHandle

减少组件对 DOM 对象的直接操作

通过 useImperativeHandle 可以手动的指定 ref 要暴露的对象,比如可以修改 MyButton 组件如下:

const MyButton = forwardRef((props, ref) => {

    useImperativeHandle(ref,()=> {
        return {
            name:'孙悟空'
        };
    });

    return <button>自定义按钮</button>
});

useImperativeHandle 的第二个参数是一个函数,函数的返回值会自动赋值给 ref(current 属性)。上例中,我们将返回值为 {name:'孙悟空'} ,当然返回孙悟空没有什么意义。实际开发中,我们可以将一些操作方法定义到对象中,这样可以有效的减少组件对 DOM 对象的直接操作。

const MyButton = forwardRef((props, ref) => {

    const btnRef = useRef();
    // useImperativeHandle 可以用来指定ref返回的值
    useImperativeHandle(ref,()=> {
        // 回调函数的返回值,会成为ref的值
        return {
            setDisabled(){
                btnRef.current.disabled = true;
            }
        };
    });

    return <button ref={btnRef}>自定义按钮</button>
});

//App.js
const App = () => {

    const btnRef = useRef();

    const clickHandler = () => {
        btnRef.current.setDisabled();
    };

    return <div>
        <MyButton ref={btnRef}/>
        <button onClick={clickHandler}>点击</button>
    </div>;
};

函数是组件的useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

第一个参数 reducer 是函数 (state, action) => newState,接受当前的 state 和操作行为。第二个参数 initialArg 是状态初始值。第三个参数 init 是懒惰初始化函数。

使用 useReducer 时,状态(state)仍是保存在组件内部的。useReducer 返回的数组中的第一个元素是当前的状态,而第二个元素是一个 dispatch 函数,用于触发状态的更新。状态是组件的本地状态,与其他组件是隔离的

用官方的一个例子看一下使用方式

import React, { useReducer } from 'react';  
  
// 定义一个 reducer 函数  
const counterReducer = (state, action) => {  
  switch (action.type) {  
    case 'increment':  
      return { count: state.count + 1 };  
    case 'decrement':  
      return { count: state.count - 1 };  
    // 你可以添加更多 case 来处理其他动作  
    default:  
      throw new Error();  
  }  
};  
  
function Counter() {  
  // 初始化 state  
  const initialState = { count: 0 };  
  
  // 使用 useReducer Hook  
  const [state, dispatch] = useReducer(counterReducer, initialState);  
  
  return (  
    <div>  
      <p>Count: {state.count}</p>  
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>  
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>  
    </div>  
  );  
}  
  
export default Counter;

dispatch 的参数是 reducer 的 action,reducer 函数根据传入的 action 执行某些逻辑,最后返回的就是新状态。

useReducer的使用实例

  • dispatch 向下传递, dispatch 可以代替 callback 的方式向子组件传递,进行从下向上的更新。优点在于 dispatch 在更新时不会重新定义,或多或少的减少了一点重新定义 callback 函数的开销,还便于子组件根据 props 判断是否需要更新。

    假如层级过深,还可以搭配 context 使用,此时使用 dispatch 代替 callback 优势更明显。因为 dispatch 在 render 时不变,不会引起使用 context 的组件执行无意义的更新。

  • 批量更新 react 对事件之外的更新不会批量处理,使用 reducer 可以避免此类问题,代码如下:

    const reducer = (state, action) => {
        switch(action.type) {
          case 'update':
            return {
              ...state,
              data: action.payload.data,
              loading: false,
            }
          default:
            return state;
        }
      }
      // 事件外触发
      dispatch({ type: 'update', payload: { data }})
    
      // 或者干脆
      const reducer = (state, newState) => {
        return {...state, ...newState}
      }
      const [state, dispatch] = useReducer(reducer, {loading: true, data: null, something: ''})
      // 触发
      dispatch({loading: false})
    

    其本质还是将 state 放到同一个地方处理,只是比 useState 更具可读性。

forceUpdate时useState 和 useReducer

useState 和 useReducer 在值不变时会跳过更新 useState举例 假设有一个组件,其中使用了 useStateuseEffect,每次点击按钮时会将 count 值加 1:

import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('count changed');
  }, [count]);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Add Count</button>
    </div>
  );
}

export default MyComponent;

在此示例中,我们使用 useEffect 监听 count 变化,并在控制台输出一条日志。如果我们每次点击按钮时,count 的值都会改变,从而导致 useEffect 执行。

但是,如果我们使用 forceUpdate,因为 count 的值没有改变,所以 useEffect 不会执行。

import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('count changed');
  }, [count]);

  const handleClick = () => {
    setCount(count + 1);
  };

  const handleForceUpdate = () => {
    // 强制组件重新渲染
    forceUpdate();
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Add Count</button>
      <button onClick={handleForceUpdate}>Force Update</button>
    </div>
  );
}

export default MyComponent;

在上面的示例中,我们添加了一个“强制更新”按钮,其中调用了 forceUpdate 方法。当我们单击此按钮时,会强制组件重新渲染,但 count 的值仍然是相同的,因此 useEffect 不会执行。

useReducer举例

假设我们有一个计数器组件,其状态由 count 属性表示,而 dispatch 函数负责更新状态。我们可以使用 useReducer 钩子来管理状态和更新。

现在假设我们传递给 useReducer 的初始状态是 { count: 0 } ,我们在组件中调用了 dispatch({type: 'INCREMENT'}) ,但是我们在处理 INCREMENT 操作时没有改变状态。

这种情况下,useReducer 在更新时会跳过更新,因为状态值没有改变。此时组件不会重新渲染,因为 React 认为应该保持相同的输出。

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state;
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  function handleClick() {
    dispatch({ type: 'INCREMENT' });
  }

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

在这个例子中,我们没有改变状态,所以每次点击“Increment”按钮时,计数器的值不会增加,而且组件也不会重新渲染。

总结

useReducer 是 useState 的替代方案,useState 能做到的事,它都能做到,甚至做得更好。useReducer 某种程度上解耦了操作逻辑(action)和后续的行为(一般是 UI 的更新),虽然代码量变多了,但是看起来更加整洁。

函数式组件的useContext

context的作用

主要用于后代之间传值通信,作用与props类似。避免嵌套props一层一层的传值

import React, { Component,Fragment, useContext } from 'react';

//创建Context实例,类似Ref
const MyContext=React.createContext()

class Dome extends Component {
    state = { Name:'jack',age:18 } 
    render() { 
        const {Name,age}=this.state
        return (
            <div className='parent' style={{width:'500px',height:'400px',background:'gray'}}>
                <h3>我是Dome组件</h3>
                <h4>我的名字是{Name}</h4>
                
           <!--组件外嵌套提供标签,用于广播其中的值Name,B组件下的所有组件包括B组件只要响应了,就都能接到-->
                <MyContext.Provider value={{Name,age}}>
            <!--下面Name的是通过props传递到B组件的,只有B组件能接到-->   
                <B Name={Name}/>
                </MyContext.Provider>
            </div>
        );
    }
}

class B extends Component {
    render() { 
        return (
            <div className='child' style={{width:'400px',height:'300px',background:'skyblue'}}>
                <h3>我是B组件</h3>
                <h4>我父亲的名字是{this.props.Name}</h4>
                <A/>
            </div>
        );
    }
}


class A extends Component {
   //类式组件响应context
    static contextType=MyContext
    render() { 
        const {Name,age}=this.context
        return (
            <div className='grand'style={{width:'300px',height:'200px',background:'orange'}}>
                <h3>我是AA组件</h3>
                <h4>我爷爷的名字是{Name},年龄{age}</h4>
                <AA/>
            </div>
        );
    }
}

function AA () {
     //函数式组件响应context
    const {Name,age}=useContext(MyContext)
      
        return (
            <div className='grand'style={{width:'250px',height:'100px',background:'pink'}}>
                <h3>我是AA组件</h3>
                <h4>我爷爷的名字是{Name},年龄{age}</h4>
            </div>
        );
}
 
export default Dome;

函数式组件的React.memo

React.memo示例

我们先从一个简单的示例入手

以下是一个常规的父子组件关系,打开浏览器控制台并观察,每次点击父组件中的 + 号按钮,都会导致子组件渲染。

const ReactNoMemoDemo = () => {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      <Child name="Son" />
    </div>
  );
};



const Child = (props) => {
  console.log('子组件渲染了');
  return <p>Child Name: {props.name}</p>;
};

render(
<ReactNoMemoDemo />
);

子组件的 name 参数明明没有被修改,为什么还是重新渲染?

这就是 React 的渲染机制,组件内部的 state 或者 props 一旦发生修改,整个组件树都会被重新渲染一次,即时子组件的参数没有被修改,甚至无状态组件。

如何处理这个问题?接下里就要说到 React.memo

React.memo介绍

React.memo是 React 官方提供的一个高阶组件,用于缓存我们的需要优化的组件

如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

让我们来改进一下上述的代码,只需要使用 React.memo 组件包裹起来即可,其他用法不变

React.memo使用

function ReactMemoDemo() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      <Child name="Son" />
    </div>
  );
}

const Child = React.memo(props => {
  console.log('子组件渲染了');
  return <p>Child Name: {props.name}</p>;
});

render(
<ReactMemoDemo />
);

再次观察控制台,应该会发现再点击父组件的按钮,子组件已经不会重新渲染了。

这就是 React.memo 为我们做的缓存优化,渲染 Child 组件之前,对比 props,发现 name 没有发生改变,因此返回了组件上一次的渲染的结果。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。

当然,如果我们子组件有内部状态并且发生了修改,依然会重新渲染(正常行为)。

React.memo的使用场景

看到这里,不禁会产生疑问,既然如此,那我直接为每个组件都添加 React.memo 来进行缓存就好了,再深究一下,为什么 React 不直接默认为每个组件缓存呢?那这样既节省了开发者的代码,又为项目带来了许多性能的优化,这样不好吗?

使用太多的缓存,反而容易带来 负提升

前面有说到,组件使用缓存策略后,在被更新之前,会比较最新的 props 和上一次的 props 是否发生值修改,既然有比较,那就有计算,如果子组件的参数特别多且复杂繁重,那么这个比较的过程也会十分的消耗性能,甚至高于 虚拟 DOM 的生成,这时的缓存优化,反而产生的负面影响,这个就是关键问题。

当然,这种情况很少,大部分情况还是 组件树的 虚拟 DOM 计算比缓存计算更消耗性能。但是,既然有这种极端问题发生,就应该把选择权交给开发者,让我们自行决定是否需要对该组件进行渲染,这也是 React 不默认为组件设置缓存的原因。

也因此,在 React 社区中,开发者们也一致的认为,不必要的情况下,不需要使用 React.memo

什么时候该用? 组件渲染过程特别消耗性能,以至于能感觉到到,比如:长列表、图表等

什么时候不该用?组件参数结构十分庞大复杂,比如未知层级的对象,或者列表(城市,用户名)等

React.memo 二次优化

React.memo 默认每次会对复杂的对象做对比,如果你使用了 React.memo 缓存的组件参数十分复杂,且只有参数属性内的某些/某个字段会修改,或者根本不可能发生变化的情况下,你可以再粒度化的控制对比逻辑,React.memo 接受两个参数:组件和一个判断函数(可选)。判断函数用于指定是否应该根据新的 props 进行重新渲染组件。如果不提供判断函数,React.memo 默认使用浅比较来对比新旧 props。

function MyComponent(props) {
  /* 使用 props 渲染 */


}
function shouldMemo(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, shouldMemo);

如果对 class 组件有了解过的朋友应该知道,class 组件有一个生命周期叫做 shouldComponentUpdate(),也是通过对比 props 来告诉组件是否需要更新,但是与这个逻辑刚好相反。

总结

对于 React.memo,无需刻意去使用它进行缓存组件,除非你能感觉到你需要。另外,不缓存的组件会多次的触发 render,因此,如果你在组件内有打印信息,可能会被多次的触发,也不用去担心,即使强制被 rerender,因为状态没有发生改变,因此每次 render 返回的值还是一样,所以也不会触发真实 dom 的更新,对页面实际没有任何影响。

函数式组件的useMemo

useMemo示例

同样,我们先看一个例子,calculatedCount 变量是一个假造的比较消耗性能的计算表达式,为了方便显示性能数据打印时间,我们使用了 IIFE 立即执行函数,每次计算 calculatedCount 都会输出它的计算消耗时间。

打开控制台,因为是 IIFE,所以首次会直接打印出时间。然后,再点击 + 号,会发现再次打印出了计算耗时。这是因为 React 组件函数重渲染的时候,不仅是 jsx,而且变量,函数这种也全部都会再次声明一次,因此导致了 calculatedCount 重新执行了初始化(计算),但是这个变量值并没有发生改变,如果每次渲染都要重新计算,那也是十分的消耗性能。

注意观察,在计算期间,页面会发生卡死,不能操作,这是 JS 引擎 的机制,在执行任务的时候,页面永远不会进行渲染,直到任务结束为止。这个过程对用户体验来说是致命的,虽然我们可以通过微任务去处理这个计算过程,从而避免页面的渲染阻塞,但是消耗性能这个问题仍然存在,我们需要通过其他方式去解决。

function UseMemoDemo() {
  const [count, setCount] = React.useState(0);

  const calculatedCount = (() => {
    let res = 0;
    const startTime = Date.now();
    for (let i = 0; i <= 100000000; i++) {
      res++;
    }
    console.log(`Calculated Count 计算耗时:${Date.now() - startTime} ms`);
    return res;
  })();

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      <div>Calculated Count: {calculatedCount}</div>
    </div>
  );
}

useMemo介绍

const memoizedValue = useMemo(() => {
  // 处理复杂计算,并 return 结果
}, []);

useMemo 返回一个缓存过的值,把 "创建" 函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算

第一个参数是函数,函数中需要返回计算值

第二个参数是依赖数组

  • 如果不传,则每次都会初始化,缓存失败
  • 如果传空数组,则永远都会返回第一次执行的结果,相当于vue的计算属性Computed
  • 如果传状态,则在依赖的状态变化时,才会从新计算,如果这个缓存状态依赖了其他状态的话,则需要提供进去。

这下就很好理解了,我们的 calculatedCount 没有任何外部依赖,因此只需要传递空数组作为第二个参数,开始改造

useMemo使用

function UseMemoDemo() {
  const [count, setCount] = React.useState(0);

  const calculatedCount = useMemo(() => {
    let res = 0;
    const startTime = Date.now();
    for (let i = 0; i <= 100000000; i++) {
      res++;
    }
    console.log(`Memo Calculated Count 计算耗时:${Date.now() - startTime} ms`);
    return res;
  }, []);

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      <div>Memorized Calculated Count: {calculatedCount}</div>
    </div>
  );
}

现在,"Memo Calculated Count 计算耗时"的输出信息永远只会打印一次,因为它被无限缓存了。

useMemo的使用场景

**何时使用 **

当你的表达式十分复杂需要经过大量计算的时候

下面示例中,我们使用状态提升,将子组件的 click 事件函数放在了父组件中,点击父组件的 + 号,发现子组件被重新渲染

const FunctionPropDemo = () => {
  const [count, setCount] = React.useState(0);

  const handleChildClick = () => {
    //
  };

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      <Child onClick={handleChildClick} />
    </div>
  );
};

const Child = React.memo(props => {
  console.log('子组件渲染了');
  return (
    <div>
      <div>Child</div>
      <button onClick={props.onClick}>Click Me</button>
    </div>
  );
});

render(<FunctionPropDemo />);

于是我们想到用 memo 函数包裹子组件,给缓存起来

const FunctionPropDemo = () => {
  const [count, setCount] = React.useState(0);

  const handleChildClick = () => {
    //
  };

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      <Child onClick={handleChildClick} />
    </div>
  );
};

const Child = React.memo(props => {
  console.log('子组件渲染了');
  return (
    <div>
      <div>Child</div>
      <button onClick={props.onClick}>Click Me</button>
    </div>
  );
});

render(<FunctionPropDemo />);

但是意外来了,即使被 memo 包裹的组件,还是被重新渲染了,为什么!

我们来逐一分析

  1. 首先,点击父组件的 + 号,count 发生变化,于是父组件开始重渲染
  2. 内部的未经处理的变量和函数都被重新初始化,useState 不会再初始化了, useEffect 钩子函数重新执行,虚拟 dom 更新
  3. 执行到 Child 组件的时候,Child 准备更新,但是因为它是 memo 缓存组件,于是开始浅比较 props 参数,到这里为止一切正常
  4. Child 组件参数开始逐一比较变更,到了 onClick 函数,发现值为函数,提供的新值也为函数,但是因为刚刚在父组件内部重渲染时被重新初始化了(生成了新的地址),因为函数是引用类型值,导致引用地址发生改变!比较结果为不相等, React 仍会认为它已更改,因此重新发生了渲染。

既然函数重新渲染会被重新初始化生成新的引用地址,因此我们应该避免它重新初始化。这个时候,useMemo 的第二个使用场景就来了

const FunctionPropDemo = () => {
  const [count, setCount] = React.useState(0);

  const handleChildClick = useMemo(() => {
    return () => {
      //
    };
  }, []);

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      <Child onClick={handleChildClick} />
    </div>
  );
};

const Child = React.memo(props => {
  console.log('子组件渲染了');
  return (
    <div>
      <div>Child</div>
      <button onClick={props.onClick}>Click Me</button>
    </div>
  );
});

render(<FunctionPropDemo />);

这里我们将原本的 handleChildClick 函数通过 useMemo 包裹起来了,另外函数永远不会发生改变,因此传递第二参数为空数组,再次尝试点击 + 号,子组件不会被重新渲染了。

对于对象,数组,renderProps(参数为 react 组件) 等参数,都可以使用 useMemo 进行缓存

既然 useMemo 可以缓存变量函数等,那组件其实也是一个函数,能不能被缓存呢?我们试一试

继续使用第一个案例,将 React.memo 移除,使用 useMemo 改造

const ReactNoMemoDemo = () => {
  const [count, setCount] = React.useState(0);

  const memorizedChild = useMemo(() => <Child name="Son" />, []);

  return (
    <div>
      <div>Parent Count: {count}</div>
      <button onClick={() => setCount(count => count + 1)}>+</button>
      {memorizedChild}
    </div>
  );
};

const Child = props => {
  console.log('子组件渲染了');
  return <p>Child Name: {props.name}</p>;
};

render(<ReactNoMemoDemo />);

尝试点击 + 号,是的,ChilduseMemo 缓存成功了!

总结

同样的,不是必要的情况下,和 React.memo 一样,不需要特别的使用 useMemo

使用场景

  1. 表达式有复杂计算且不会频发触发更新
  2. 引用类型的组件参数,函数,对象,数组等(一般情况下对象和数组都会从 useState 初始化,useState 不会二次执行,主要是函数参数)
  3. react 组件的缓存

函数式组件的useCallback

深入理解useMemo与useCallBack的区别

前言:在阅读本文前需了解基本数据类型和引用数据类型的区别。

true === true       // true
false === false     // true
1 === 1             // true
'a' === 'a'         // true

{} === {}                 // false
[] === []                 // false
() => {} === () => {}     // false
  • React.memo()
  • React.useCallback()
  • React.useMemo()

*以下所有例子将采用函数式组件进行说明

React.memo()

React中当组件的props或state变化时,会重新渲染视图,但实际开发中并不需要多次的渲染,直接导致的结果是用户的体验感不好。 子组件

function ChildComp () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

父组件

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp />
    </div>
  );
}

结果:子组件信息被多次打印

将子组件用React.memo()包一层,只有当子组件的props和state变化时,才会重新渲染子组件。

子组件(写法一)

import React, { memo } from 'react'

const ChildComp = memo(function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
})

子组件(写法二)

import React, { memo } from 'react'

let ChildComp = function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

ChildComp = memo(ChildComp)

结果:子组件信息只在在父组件被初次渲染的时候打印了一次

React.useCallback()

父组件

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  const [ name, setName ] = useState('hi~')
  const changeName = (newName) => setName(newName)  // 父组件渲染时会创建一个新的函数

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

结果:子组件的信息仍然打印了多次

原因:

  • 点击按钮导致父组件重新渲染(count值发生了变化),并且会重新创建changeName函数(函数也属于引用数据类型,每次重新创建时地址不同),子组件的属性(函数)发生了变化,因此子组件被重新渲染

解决:修改父组件的changeName方法,用useCallback钩子函数包裹一层

import React, { useCallback } from 'react'

function ParentComp () {
  // ...
  const [ name, setName ] = useState('hi~')
  // 每次父组件渲染,返回的是同一个函数引用
  const changeName = useCallback((newName) => setName(newName), [])  

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

useCallback()起到了缓存的作用,即使父组件渲染了,被useCallback()的函数也不会重新创建

React.useMemo()

useMemo 和 useCallback 十分相似,useCallback 用来缓存函数对象,useMemo 用来缓存函数的执行结果。在组件中,会有一些函数具有十分的复杂的逻辑,执行速度比较慢。闭了避免这些执行速度慢的函数返回执行,可以通过 useMemo 来缓存它们的执行结果,像是这样:

const result = useMemo(()=>{
    return 复杂逻辑函数();
},[依赖项])

//举例
const someEle = useMemo(()=>{
    return <Some a={a} b={b}/>;
}, [a, b]);

组件也是函数,也可以缓存组件,

useMemo 中的函数会在依赖项发生变化时执行,注意!是执行,这点和 useCallback 不同,useCallback 是创建。执行后返回执行结果,如果依赖项不发生变化,则一直会返回上次的结果,不会再执行函数。这样一来就避免复杂逻辑的重复执行。

父组件传递给子组件的数据不是字符串而是对象,React.Memo是否还有用? 子组件

import React, { memo } from 'react'

const ChildComp = memo(function ({ info, onClick }) {
  console.log('render child-comp ...')
  return <>
    <div>Child Comp ... {info.name}</div>
    <button onClick={() => onClick('hello')}>改变 name 值</button>
  </>
})
123456789

父组件

import React, { useCallback } from 'react'

function ParentComp () {
  // ...
  const [ name, setName ] = useState('hi~')
  const [ age, setAge ] = useState(20)
  const changeName = useCallback((newName) => setName(newName), [])
  const info = { name, age }    // 复杂数据类型属性

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp info={info} onClick={changeName}/>
    </div>
  );
}

结果:子组件信息被打印了多次

原因:跟传递函数原因类似,当点击按钮时,父组件会被重新渲染,info会生成一个新的对象(拥有新的地址),进而导致子组件被重新渲染

解决:使用useMemo将该对象包裹住

useMemo有两个参数:

  • 第一个参数是函数,返回的对象指向同一个引用,不会创建新的对象
  • 第二参数是数组,当数组中的变量发生改变时,第一个参数的函数才会返回一个新的对象
function ParentComp () {
  // ....
  const [ name, setName ] = useState('hi~')
  const [ age, setAge ] = useState(20)
  const changeName = useCallback((newName) => setName(newName), [])
  const info = useMemo(() => ({ name, age }), [name, age])   // 包一层

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp info={info} onClick={changeName}/>
    </div>
  );
}

三种的对比总结 --重要

  • 上述三种方法都是为了解决组件不必要的重复渲染问题 ,但针对的情况有所不同

  • React.Memo(),前提是子组件没有接收父组件传递的引用类型参数(函数,对象,数组),使用:用React.Memo包裹子组件,就是让无关的子组件不再重新渲染

  • React.useMemo(),计算属性,缓存计算结果的,使用于组件计算的函数,让计算依赖值的技术结果不发生变化时,计算函数不再创建,当依赖数组中的值发生变化时,useMemo会重新计算函数的结果,并将结果缓存起来。在组件重新渲染时,如果依赖数组中的值没有变化,则直接返回缓存的结果,避免不必要的计算。

  • React.useCallBack(),当子组件接收来自父组件的函数,使用:将函数用useCallback(函数,[])包裹,它是用来缓存函数,它接收一个函数和一个依赖数组作为参数。如果依赖项(那个函数)不发生变化,则一直会返回上次的结果,不会再执行函数。当依赖数组中的值发生变化时,useCallback会返回一个新的函数。

自定义hook

import { useRef, useState, useEffect} from "react";

function useMousePosition(){
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  

 // 防抖函数  
 function debounce(func, wait) {  
  let timeout;  
  return function() {  
    const context = this;  
    const args = arguments;  
    clearTimeout(timeout);  
    timeout = setTimeout(() => func.apply(context, args), wait);  
  };  
} 
function updateMouse(e: MouseEvent) {  
  setX(e.clientX);  
  setY(e.clientY);  
}  
// 防抖后的事件处理函数  
const debouncedUpdateMouse = debounce(updateMouse,5); // 1000 毫秒 = 1 秒


// 监听鼠标位子
useEffect(() => {
    window.addEventListener('mousemove', debouncedUpdateMouse);
  })
  
  
  return [x, y];
}

function Hello() {
 const [x, y] = useMousePosition();
 return (
    <>
      <p>当前鼠标位置:{x},{y}</p>
    </>
  );
};

export default Hello;



模拟错误边界

在函数式组件中,没有直接的“生命周期方法”或“特殊属性”来实现错误边界,因为错误边界通常是类组件的一个特性。不过可以使用React.useEffectReact.useState来模拟错误边界的行为。下面是一个简单的例子:

import React, { useState, useEffect } from 'react';  
const ErrorBoundary = ({ children }) => {  
const [hasError, setHasError] = useState(false);

useEffect(() => {  
    const errorHandler = (error) => {  
    console.error(error);  
    setHasError(true);  
    };  

    const componentDidCatch = (error, errorInfo) => {  
        console.error(error, errorInfo);  
        setHasError(true);  
        };  

    // 捕获全局错误  
    window.addEventListener('error', errorHandler);  
    // 清理函数  
    return () => {  
        window.removeEventListener('error', errorHandler);  
    };  
}, []);  

if (hasError) {  
    // 你可以自定义这里的错误显示  
    return <div>Something went wrong.</div>;  
}  

// 使用try/catch包裹子组件,但这并不会捕获所有类型的错误  
try {  
    return children;  
    } catch (error) {  
        console.error(error);  
        return <div>Caught an error in ErrorBoundary.</div>;  
    }  
};  
export default ErrorBoundary;

在这个例子中,我们创建了一个ErrorBoundary组件,它尝试捕获全局错误以及直接发生在其内部的错误。然而,请注意,由于JavaScript和React的工作方式,这种方法可能无法捕获所有类型的错误。特别是,它可能无法捕获异步代码中的错误,除非这些错误被明确地传递给错误边界。

Fragment碎片标签和空标签

一般做法div标签

坏处,代码中div会造成多层冗余嵌套,

import React, { useState } from 'react';  
  
function Dome() {  
    const [input1, setInput1] = useState('');  
    const [input2, setInput2] = useState('');  
  
    return (  
        <div>  
            <input type="text" value={input1} />  
            <input type="text" value={input2} />  
        </div>  
    );  
}  
  
export default Dome;

Fragment碎片标签

坏处,在引入中多一步引入

好处,显示页面时,源码会自动将 <Fragment>给除掉,能且只能接受的属性 Key

import React, { useState } from 'react';  
  
function Dome() {  
    const [input1, setInput1] = useState('');  
    const [input2, setInput2] = useState('');  
  
    return (  
        <Fragment>  
            <input type="text" value={input1}  />  
            <input type="text" value={input2} />  
        </Fragment>  
    );  
}  
  
export default Dome;

空标签

坏处,没办法给空标签指定属性,如类名给,key值什么的

好处,简单不用引入,源码会自动将空标签给除掉

import React, { useState } from 'react';  
  
function Dome() {  
    const [input1, setInput1] = useState('');  
    const [input2, setInput2] = useState('');  
  
    return (  
        <>  
            <input type="text" value={input1}  />  
            <input type="text" value={input2} />  
        </>  
    );  
}  
  
export default Dome;

懒加载与Suspense

懒惰加载的目的是使网页首次加载变快,方法是将代码划分成若干chunk, 用户最不可能访问到的页面优先使用 lazy loading, 根据访问到的路径实时按需加载相应的代码。这样,用户不需要一开始就下载大量的代码,因为其中一些代码可能不需要用到。

导入Suspense

import React, { Suspense } from "react";

React.Suspense 是一个新的 React 组件,它可以阻塞渲染,在某个异步操作结束之前暂停渲染,等待异步操作完成后再恢复渲染。在使用 React.lazy() 函数延迟加载组件时,可以使用 React.Suspense 组件来包装异步组件,以便在组件加载期间显示一个占位符或者 loading 状态,让用户知道组件正在加载中。导入Suspense组件的目的是,提供按需加载时的UI fallback,即过渡画面,因为加载需要时间,不使用 Suspense React 会报错。

修改 import 语句

例如:

import QuoteDetail from "./pages/QuoteDetail";
//改为:
const QuoteDetail = React.lazy(() => import("./pages/QuoteDetail"));

这些这样修改的组件是假定用户访问的可能性不高的页面,用户访问的可能性越低,使用懒惰加载的价值就越大。

Suspense需要fallback 属性

App.js 中,将全部路由在 Suspense 组件中,当异步组件被包装在 React.Suspense 组件中时,如果异步加载的组件还未加载完成,则 React.Suspense 组件会自动渲染 fallback 属性所指定的内容,直到异步组件加载完成后再重新渲染组件。 只需要这3步就可实现懒惰加载,对于小的App来说,使用懒惰加载只是一个可选项,可使用可不使用。但对于大型App,懒惰加载的优势会得到极大的体现。

完整的代码例子如下:

import React, { Suspense } from "react";
import { Route, Switch, Redirect } from "react-router-dom";
import Layout from "./components/layout/Layout"
import LoadingSpinner from "./components/UI/LoadingSpinner";

const NewQuote = React.lazy(() => import("./pages/NewQuote"));
const QuoteDetail = React.lazy(() => import("./pages/QuoteDetail"));
const NotFound = React.lazy(() => import("./pages/NotFound"));
const AllQuotes = React.lazy(() => import("./pages/AllQuotes"));

function App() {
  return (
    <Layout>
      <Suspense
        fallback={

          <div className="centered">
            <LoadingSpinner />
          </div>

       }
      >
       <Switch>
          <Route path="/" exact>
            <Redirect to="/quotes" />
          </Route>
          <Route path="/quotes" exact>
            <AllQuotes />
          </Route>
          <Route path="/quotes/:quoteId">
            <QuoteDetail />
          </Route>
         <Route path="/new-quote">
           <NewQuote />
         </Route>
         <Route path="*">
           <NotFound />
          </Route>
        </Switch>
      </Suspense>
    </Layout>
  );
}
export default App;

react的Portal门户

在 React 中,Portal 是一种能够将子组件渲染到父组件之外的机制。这对于实现一些 UI 功能非常有用,比如创建模态框、对话框等场景。Portal中文门户的意思,表示创建个试图,将视图渲染在根组件之上

使用 Portal 很简单,只需要在 React 组件中使用 ReactDOM.createPortal 方法将需要渲染的子组件作为第一个参数,将需要渲染到的父组件 DOM 元素作为第二个参数即可。例如:

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

function Modal () {
  render() {
    return ReactDOM.createPortal(
      <div className="modal">
        <div className="modal-content">
          <h2>{this.props.title}</h2>
          <p>{this.props.content}</p>
          <button onClick={this.props.onClose}>Close</button>
        </div>
      </div>,
      document.getElementById('modal-root')
    );
  }
}
function App () {
  state = { showModal: false };
  handleShowModal = () => {
    setState({ showModal: true });
  }

  handleHideModal = () => {
    setState({ showModal: false });
  }

  render() {
    return (
      <div>
        <button onClick={this.handleShowModal}>Show Modal</button>
        {this.state.showModal && (
          <Modal
            title="Modal Title"
            content="This is the modal content."
            onClose={this.handleHideModal}
          />
        )}
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

在这个例子中,我们将 Modal 组件作为子组件渲染到了 <div id="modal-root"> 元素中,而不是 App 组件中。这样就可以在子组件中创建模态框等 UI 功能了。

不常用的hook

了解即可

UseDebugValue

用来给自定义钩子设置标签,标签会在 React 开发工具中显示,用来调试自定义钩子,不常用。

调用 hook 很多次的时候,需要进行 区分 使用

UseDeferredValue

useDeferredValue 用来设置一个延迟的 state,比如我们创建一个 state,并使用 useDeferredValue 获取延迟值:

useDeferredValue 需要一个state的作为参数,会为该state创建一个延迟值 当设置了延迟值后,每次state修改时都会触发两次重新的渲染 这两次执行对于其他的部分没有区别,但是延迟值两次执行的值是不同的 第一次执行时 延迟值是state的旧值, 第二次执行时,延迟值是state的新值 延迟值,总是会比原版的state,慢一步更新

const [queryStr, setQueryStr] = useState('');
const deferredQueryStr = useDeferredValue(queryStr);

上边的代码中 queryStr 就是一个常规的 state,deferredQueryStr 就是 queryStr 的延迟值。设置延迟值后每次调用 setState 后都会触发两次组件的重新渲染。第一次时,deferredQueryStr 的值是 queryStr 修改前的值(旧值),第二次才是修改后的值(新值)。换句话,延迟值相较于 state 来说总会慢一步更新。

延迟值可以用在这样一个场景,一个 state 需要在多个组件中使用。一个组件的渲染比较快,而另一个组件的渲染比较慢。这样我们可以为该 state 创建一个延迟值,渲染快的组件使用正常的 state 优先显示。渲染慢的组件使用延迟值,慢一步渲染。当然必须结合 React.memo 或 useMemo 才能真正的发挥出它的作用。

UseTransition

当我们在组件中修改 state 时,会遇到复杂一些的 state,当修改这些 state 时,甚至会阻塞到整个应用的运行,为了降低这种 state 的影响,React 为我们提供了 useTransition,通过 useTransition 可以降低 setState 的优先级。

const [isPending, startTransition] = useTransition();

useTransition 会返回一个数组,数组中有两个元素,第一个元素是 isPending,它是一个变量用来记录 transition 是否在执行中。第二个元素是 startTransition,它是一个函数,可以将 setState 在其回调函数中调用,这样 setState 方法会被标记为 transition 并不会立即执行,而是在其他优先级更高的方法执行完毕,才会执行。

除了 useTransition 外,React 还直接为为我们提供了一个 startTransition 函数,在不需要使用 isPending 时,可以直接使用 startTransition 也可以达到相同的效果。

// startTransition 的回调函数中设置setState会其他的setState生效后才执行
startTransition(()=>{
    setFilterWord2(e.target.value);
});

UseId

生成唯一 id,使用于需要唯一 id 的场景,但不适用于列表的 key。

react的proxy和跨域问题

前端解决跨域-proxy

前端处理跨域的原理,浏览器为了安全,防止别的攻击,所以有同源策略,所以接收不到跨域的信息。解决方法是在客户端开一个node服务器,客户端向node服务器请求,node服务器向外部服务器请求,node服务器没有同源策略,所以可转接信息

react脚手架

在react脚手架的SRC文件中建立setupProxy文件 react会自动识别(虽然我并没有代理成功,可能在vite中的react有点问题)(好像是因为我没下这个插件。。。)

 const {createProxyMiddleware}=require('http-proxy-middleware');
 module.exports=function(app){
    app.use(
    createProxyMiddleware('/api',{
             target:'http://localhost:5001/',
             changOrigin:true,
             pathRewrite:{'^/api':""}
         })
)
}

vite中的代理

在vite.config.js中配置

server: {
    port: 3000,
    proxy: {
      // 接口地址代理
      '/api': {
        target: 'http://localhost:5001', // 接口的域名
        secure: false, // 如果是https接口,需要配置这个参数
        changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  }

后端跨域的处理

中间件的使用

import express from 'express';
 const app=express()

 app.use( (req,res,next) => {
    console.log('有人请求服务器在',Date());
 next()
  })


//跨域的中间件
  app.use( (req,res,next) => {
  res.setHeader('Access-Control-Allow-Origin','*')
 	next()
  })


  app.get('/students',(req,res) => {
     const students=[
        {id:'001',name:'tom',age:19},
        {id:'002',name:'tony',age:19},
        {id:'003',name:'jerry',age:19}
     ]
     res.send(students)
  })
  app.listen(5001,(err) => {
    if(!err)console.log('http://localhost:5000/students已启动',Date());
  })

使用cors()包

var express = require('express');
var cors = require('cors');
var app = express();

// 开发环境设置,生产环境需要配置安全的参数
app.use(cors());

app.get('/', function (req, res) {
 res.status(200).send('hello itas109');
});

app.listen(8080);

react样式的处理

行内样式和类样式

使用行内样式时,属性 style={} 里面要传一个对象。可以直接写入一个对象,也可以写入一个已经声明好的对象。

使用样式文件时,用 import 导入样式文件,在标签上添加 className 属性,由于 JSX 就是 JavaScript,class 需要写成 className ,class 是 JavaScript 的保留字。 从性能角度来说,CSS 的 class 通常比行内样式更好。

   import React, { Component } from 'react';
   import "./common.css";
  
  class Box extends Component {
    constructor(){
      super();
      this.state ={
        styleObj:{
          fontSize:'50px'
        }
      }
    }
    render() {
      return (
        // 使用class,container是在common.css中声明的一个样式
        <div className="container">
          {/* 行内样式 */}
          <h1 style={{'fontSize':'20px'}}>标题1</h1>
          <h1 style={this.state.styleObj}>标题2</h1>
        </div>
      );
    }
  }
  export default Box;

模块化样式

使用CSS Modules:CSS Modules是一种让CSS文件具有局部作用域的技术。通过使用CSS Modules,每个组件的样式都可以被限定在自己的作用域内,从而避免样式冲突。

新建后缀名为.module.css的样式文件,例如新建common.module.css的样式文件内容如下:

.success{color:green;font-size:20px;}
.error{color:red;font-size:20px;}

使用的时候,通过 import 导入样式文件,通过文件名[属性]来使用这个样式。

import React, { Component } from 'react';
import classes from "./common.module.css"

class Box extends Component {
  render() {
    return (
      <div>
        <span className={clesses.error}>异常信息</span>
      </div>
    );
  }
}

export default Box;

动态切换样式

通过 classNames 插件来完成,根据不同的条件判断,应用不同的样式。

在终端执行下面的命令,安装 classNames yarn add classnames -S

使用的时候: 1、通过 import 先导入classNames 插件; 2、使用 classNames.bind() 方法建立绑定关系; 3、声明样式对象,属性名为样式名称,值为boolean,true表示应该该样式;false表示不应用该样式; 4、在标签上添加 className 属性,值为该样式对象;

具体使用方式如下:

/* common.css 文件内容 */
.success{color:green;font-size:20px;}
.error{color:red;font-size:20px;}

react 代码:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames/bind';
import styles from './common.css'

let cx = classNames.bind(styles); //建立绑定关系

class Box extends Component {
  render() {
    let names = cx({	//声明样式对象
      success:true,
      error:this.props.error	//从标签属性中读取传过来的值
    })
    return (
      <div>
        <h1 className={names}>这是提示信息</h1>
      </div>
    );
  }
}

ReactDOM.render(
  <Box  error={false} />,
  document.getElementById('root')
);

使用这种方式的好处就是可以动态切换DOM元素中应用的样式。

CSS-in-JS

使用CSS-in-JS库:CSS-in-JS是将CSS代码直接写入JavaScript代码中的一种方案。通过使用CSS-in-JS库,样式可以被组件化,从而避免样式冲突。 在 react 里一切皆组件,所以能不能把 clss 也定义成组件模块呢?当然是可以的。styled-components是针对 React 写的一套 css-in-js 框架,简单来讲就是可以在 js 中写 css。

“CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义。

注意此功能并不是 React 的一部分,而是由第三方库提供。 React 对样式如何定义并没有明确态度;如果存在疑惑,比较好的方式是和平时一样,在一个单独的 *.css 文件定义你的样式,并且通过 className 指定它们。

我们来看如何使用 styled-components 插件。

在终端中执行下面的命令,安装 styled-components 插件

yarn add styled-components -S

新建一个样式文件,注意这个样式文件的后缀是 .js 1、通过 import 导入 styled-components; 2、使用 styled 定义样式内容,可以通过属性传参,也可以使用 sass 语法;

//common.js 样式文件
import styled from 'styled-components'

//这种定义的写法类似于 <div class="Container"></div>
const Container = styled.div`
    width:500px;
    height:500px;
    background:${(props)=>props.color};
    font-size:30px;
    h1 {
      font-size:50px;
    }
`
export {
  Container
}

在react中使用的时候:

import React, { Component } from 'react';
import {Container} from './common'

class Box extends Component {
  render() {
    return (
      <Container color="red">
        <h1> hello world</h1>
        这里是内容
      </Container>
    );
  }
}

ReactDOM.render(
    <Box />,
  document.getElementById('root')
);

最终渲染到页面的结果如下:

这种方式的优点是可以动态传递参数。

使用BEM命名规范

BEM是一种CSS命名规范,通过为CSS类名添加块(block)、元素(element)和修饰符(modifier)的前缀,可以有效地避免样式冲突。

总结

  1. 行内样式和类样式,通常不推荐将 style 属性作为设置元素样式的主要方式。在多数情况下,应使用 className 属性来引用外部 CSS 样式表中定义的 class。style 在 React 应用中多用于在渲染过程中添加动态计算的样式。
  2. 模块化样式,样式文件的后缀为.module.css,使用的时候以对象的形式来调用样式。
  3. 动态切换样式,通过 classNames 插件来完成,通过对样式属性设置 true 或者 false,来设置是否将该样式应用到DOM对象中。
  4. CSS-in-JS,在js文件中写css,样式文件的后缀名为 .js,这种方式最大的优点就是可以传递参数。
转载自:https://juejin.cn/post/7352770048513409075
评论
请登录