React

文章目录


一、React 起步

<div id="app"></div>
    <!-- react 核心库 -->
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <!-- react提供的与dom相关功能的库 -->
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <!-- 主要是将es6代码转为es5,并提供jsx语法支持 -->
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

    <!-- 设置脚本使用babel -->
    <script type="text/babel">
        console.log(ReactDOM); // react一些方法
        // 主要使用render渲染一个dom组件到指定的容器里
        // 第一个参数是 一个组件,第二个参数是要渲染的dom容器
        ReactDOM.render(<div>Hello React</div>,document.querySelector("#app"))
        // render渲染内容是不可变的,需要改变内容则要重新调用render函数渲染
        setTimeout(()=>{
            ReactDOM.render(<div>React change</div>,document.querySelector("#app"))
        },2000)
    </script>

二、jsx(核心)

  1. react 使用jsx代替常规的js操作
  2. jsx 是js的语法扩展(js中可以使用jsx语法。jsx是属于js的一部分,可以和js混合用)
  3. jsx 是类似xml结构(一般是有开始和结束标记。可以理解为直接在js里面可以使用html标记)
  4. jsx 特点:
    a. jsx 执行速度很快,直接编译为js执行,运行经过优化,速度更快。
    b. 类型更安全,在编译的时候可以发现基础的错误。
    c. 使用jsx和和js完美融合,使用jsx描述ui更便捷

注意:
class需要使用 className
style中必须使用对象设置 style={ {backgroundColor:"green"} }
定义虚拟 DOM 时,不要写引号
标签中混入 JS 表达式需要使用 { }
只能有一个根标签
标签必须闭合,单标签结尾必须添加 / :<input type="text" />
标签首字母小写,则把标签转换为 HTML 对应的标签,若没有,则报错
标签首字母大写,则渲染对应组件,若没有定义组件,则报错

jsx使用

1. jsx把html标签作为一个值使用

jsx是可以把html标签作为一个值使用(普通的js里面,不能把html标签作为一个变量值)
jsx 类似于html标签,可以直接把html标签当作一个js的值使用,jsx只有一个根元素,可以嵌套多个元素

// jsx使用{/* */}注释jsx语法
const div1 = <div>
                <span>hello</span>
                <div>React 
                    {/* <em>!!!!</em> */}
                </div>
            </div>
ReactDOM.render(div1,document.querySelector("#app")) // 挂载dom

2. jsx 可以在{}嵌入js单行表达式

// 1 { } 里面使用js变量
    const num = 100
    const div1 = <div>{ num }</div>
    
// 2 { } 进行js的运算
    const div2 = <div>{ 100 * 100 + 17}</div>
    
// 3 { } 三元表达式
    const div3 = <div>{ num >= 100? "Yes":"No" }</div>
    
// 4 {} 执行函数(执行函数需要添加小括号)
    function demo() {
        return "demo"
    }
    const div4 = <div>{ demo() }</div>
    
ReactDOM.render(div4,document.querySelector("#app")) // 挂载dom

3. jsx的style样式

style样式,react官方推荐使用style内联样式,样式用js对象方式描述

// jsx里面元素的 class属性为了和js的class关键词区分,class属性使用className
       const div6 = <div className="box1 box2">这是Style渲染的内容</div>

// jsx中的html标签的属性可以正常使用,如果属性需要使用js变量,则需要用{}
       let box1 = "box1"
       let box2 = "box2"
       // 注意:如果需要加两个样式 需要用空格隔开
       const div7 = <div className={box1 +" "+box2}>div7</div>

 ReactDOM.render(div7,document.querySelector("#app")) // 挂载dom

一个一个设置

import logo from './assets/react.svg'

function App() {
  let logoStyle = {
    width: "100vh",
    height: 200,
    backgroundColor: "gray" // style的样式多个单词用驼峰命名
  }

  return (
    <div>
      {/*jsx里面元素的 class属性为了和js的class关键词区分,class属性使用className */}
      <img src={logo} 
        className='logoImg' 
        style={logoStyle} 
      />
    </div>   
  )
}
export default App

展开语法设置

tips:ES6 里面的展开运算符必须放在 {…}或者 […]中

import logo from './assets/react.svg'

function App() {
  
  let logoStyle = {
    className:'logoImg',
    style: {
      width:200,
      height:200,
      backgroundColor:"gray",
    } 
  }

  return (
    <div>
      <img src={logo} 
        alt=''
      // 注意:这里的展开符并不是ES6里面的展开运算符,这是jsx独有的功能支持
        {...logoStyle}
      />
    </div>   
  )
}
export default App

4. jsx插入数组

jsx允许插入数组,如果数组的成员是jsx,会自动展开成员

// 1. 数组成员是jsx,可以自动展开渲染
// 批量渲染的jsx元素,必须添加key属性设置唯一值,主要是为了提高diff算法效率
   const div1 = [<div key={1}>1</div>,<div key={2}>2</div>,<div key={3}>3</div>]
   ReactDOM.render(div1,document.querySelector("#app")) // 挂载dom

// 2. 数组是非jsx成员,不能直接展开,需要转为jsx语法
   const list = [{name:"zs",age:19},{name:"anna",age:25},{name:"kk",age:16}]
   const div2 = <section>
                   {list.map(item=>{
                       return <div key={item.name}>{item.name}-{item.age}</div>
                   })}
               </section>
               
ReactDOM.render(div2,document.querySelector("#app"))

// 3. jsx元素的事件就是dom元素事件添加一个on前缀,并绑定一个函数
   let count = 1
   function addCount(){
       count++
       renderDiv3()
   }
   function renderDiv3(){
       const div3 = <div>
                       <div>{count}</div>
                       <button onClick={addCount}>点击count+1</button>
                    </div>

        ReactDOM.render(div3,document.querySelector("#app"))
   }
 renderDiv3()

三、组件

react项目是由组件构成的,组件是react的基本单位。

  1. 组件内部使用jsx语法描述ui界面,一个组件必须返回一个jsx
  2. 组件的名称遵循大驼峰命名规范

1. 类组件

使用class定义组件(比较早的用法,实际开发中较少用)

console.log(React);
// class类必须继承React的Component组件类,才能使用组件的方法和属性
    class Demo extends React.Component{
        // Component类的属性,state是组件内部的状态,state的值改变,会触发render函数的重新执行(组件的重绘)
        // 一般state状态用于更新组件
        state={
            age:1
        }
        addAge = ()=>{ // 需要用箭头函数,this才是执行当前对象
            // setState也是Component类的方法,用于修改state值,只有用setState修改state,才会触发render函数的重绘
            this.setState({
                age: ++this.state.age // 先++,不然state值还是1,不会触发事件
            })
        }
        name = "Anna"
        // render函数是Component类的方法,该方法可以渲染一个返回的jsx
        render() {
            return <div>
                        <p>Hello{this.name}</p>
                        <p>年龄:{this.state.age}</p>
                        <button onClick={this.addAge}>age增加</button>
                    </div>
        }
    }
ReactDOM.render(<Demo/>,document.querySelector("#app"))

2. 函数组件

使用函数定义一个组件,一个函数就是一个组件,函数返回值是一个jsx

function User(){
       let name = "Mario"
       let age = 22
       return <div>
                   <p>Hello{name},</p>
                   <p>年龄:{age}</p>
               </div>
   }

// 1. render 函数直接渲染组件
   ReactDOM.render(<User />,document.querySelector("#app"))
// 2. 可以在jsx里面把组件做为一个元素直接调用
   let div1 = <div><User /></div>
   ReactDOM.render(div1,document.querySelector("#app"))

3. 组件核心属性 state

state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。

class Weath extends React.Component {
    state = {
        isHot:true
    }

    // 事件采用箭头函数 + 赋值语句形式
    changeWeath = ()=>{
        this.setState({ // 必须使用setState修改state的属性
            isHot:!this.state.isHot
        })
    }

    render(){
        return <h2 onClick={this.changeWeath}>今天天气很{this.state.isHot?"炎热":"凉爽"}</h2>
    }
}

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

state的补充

class User extends React.Component{
    state = {
        count:1
    }
    addCount = ()=>{
        // count state 修改完成后触发第二个回调函数参数
        this.setState({count:this.state.count+1},()=>{
            console.log("count 修改完成");
        })
    }
    render(){
        return <div>
                <div>count:{this.state.count}</div>
                <button onClick={this.addCount}>点击</button>
            </div>
    }
}

4. 组件核心属性 props (父传子)

props是组件的自定义属性,传递了属性都会放入props对象里面。
props属性可以让是任意数据类型,是只读属性,组件内部不能修改自身的props。

class User extends React.Component{  
    render(){
        console.log(this.props);
        return <div>
                    <h3>用户信息:</h3>
                    <p>年龄:{this.props.data.name }</p>
                    <p>age:{this.props.data.age}</p>
                </div>
    }
}

class App extends React.Component{
    userInfo = {name:"Anna",age:28}
    render(){
        return <div>
                    <h3>App 组件</h3>
                {/* {}是表示用js表达式,其值是一个对象(动态属性)。 */}
                    <User data = {this.userInfo} />
                    <User data = {{name:"zs",age:19}} />
                    <User />
                </div>
    }
}
        
ReactDOM.render(<App/>,document.querySelector("#root"))

props属性的 PropTypes /defaultProps

react15.5以后需要单独引入prop-types库
User.propTypesUser.defaultProps 可以看作在身上添加属性,利用 static 关键词就能在类内部进行声明。因此所谓简写只是从类外部移到类内部。

<!-- 引入props属性类型校验库,可使用PropTypes对象设置数据类型 -->
 <script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
<script type="text/babel">
class User extends React.Component{  
     render(){
         // console.log(this.props);
         return <div>
                     <h3>用户信息:</h3>
                 </div>
     }
 }
// 如果props属性类型不正确,会用warnning方式提醒
  console.log(PropTypes);
  User.propTypes = {
      data:PropTypes.object,// 对象
      name:PropTypes.string,// 字符串
      age:PropTypes.number,// 数值
      list:PropTypes.array,// 数组
      dispatch:PropTypes.func,//函数
      opation:PropTypes.oneOfType([PropTypes.string,PropTypes.number]),// 符合多个类型中的一种
      pop:function(props,propName,comName){ // 自定义校验器               
      // 第一个参数props对象,第二个参数是pop的名称,第三个是组件名称
          console.log(props,propName,comName);
      }
  }
// 设置props属性的默认值
   User.defaultProps = {
       data:{name:"XXX",age:"未知"}
   }

class App extends React.Component{
    render(){
        return <div>
                    <h3>App 组件</h3>
                {/* {}是表示用js表达式,其值是一个对象(动态属性)。 */}
                    <User pop={22} opation={[]} />
                </div>
    }
}

   ReactDOM.render(<App/>,document.querySelector("#root"))
</script>

函数组件:
下载:cnpm i prop-types

import PropTypes from "prop-types" //引入
import { connect } from "react-redux";

// 从props组件中解构
function Layouts({profile,dispatch}) {
  return (
    <div>
      首页
    </div>
  )
}

// 自定义props属性的 默认值
Layouts.defaultProps = {
    profile: "",
    dispatch:()=>"",
}
// 自定义props属性的 数据类型
Layouts.prototype = {
    profile:PropTypes.oneOfType([PropTypes.string,PropTypes.object]),
    dispatch:PropTypes.func,
}

export default connect(
    state=>({profile:state.user.profile}),
    dispatch=>({dispatch})
)(Layouts)

state和props的区别:

  1. state 是组件状态,可以修改,state修改,触发render函数执行,重绘组件
  2. props 是组件属性,不可修改,props只能通过父组件传值。不会影响render函数的指向。

插槽

class User extends React.Component{
    render(){
        return <div>
                {this.props.children}
            </div>
    }
}

class App extends React.Component{
    render(){
        return <div>
                <h2>App:</h2>
                {/* 把函数传递到子组件,子组件调用该函数并传递参数 */}
                <User>
                    {/* 组件包裹的元素或放入props属性的children属性上 */}
                    <div>这是插槽内容</div>
                </User>
            </div>
    }
}

ReactDOM.render(<App/>,document.querySelector("#root"))

5. 回调函数 (子传父 )

class User extends React.Component{
    file = {name:"lolo",age:19}
    render(){
        return <div>
                <button onClick={()=>this.props.getFile(this.file)}>点击传递</button>
            </div>
    }
}

class App extends React.Component{
    getFile = (data)=>{
        console.log(data);
    }
    render(){
        return <div>
                <h2>App:</h2>
                <User getFile={this.getFile} />
            </div>
    }
}

ReactDOM.render(<App/>,document.querySelector("#root"))

6. 组件的条件渲染

class User extends React.Component{
    age = 19
    setAge = ()=>{
        if (this.age>=18){
            return <div>成年</div>
        } else {
            return <div>未成年</div>
        }
    }
    render(){
        return <div>
            {/* 根据条件是否渲染组件,可以利用逻辑运算符的顺序,如果&&的左边为true会判断右边,如果左边为false不会执行右边 */}
                { this.age>=18 && <div>成年</div>}
            {/* 三元表达式进行条件判断渲染,如果为null表示不渲染任何组件 */}
                {this.age>=18?<div>成年</div>:<div>未成年</div>}
            {/* 如果条件比较复杂,可以单独写函数进行判断 */}
                {this.setAge()}
            </div>
    }
}

7. 组件的生命周期函数

常用(重要)的三个钩子:

componentDidMount:组件第一次渲染完成,这里可以开启定时器、发送网络请求、订阅消息等。
render:组件更新,一直会执行的钩子
componentWillUnmount:组件将要被销毁,这里可以关闭定时器、取消订阅等。

旧版

即将废弃的三个钩子:componentWillMountcomponentWillReceivePropscomponentWillUpdate
在这里插入图片描述

1. 挂载(旧)

// 1 构造函数,组件挂载之前调用
constructor(props){
    super(props) // 在构造函数内部要使用props,需要super继承props
    console.log("constructor");
}

// 3. componentDidMount 组件第一次挂载完成(第一次执行完render函数触发)
componentDidMount(){
    console.log("componentDidMount");
}
// 2.初始化展示、状态更新之后
render(){
    console.log("render");
}

2. 更新(旧)

// 3. shouldComponentUpdate 控制组件更新的阀门,render 函数执行前执行
// 注意:这个钩子如果不写,底层也会补一个。
shouldComponentUpdate(){ // 主要用于优化性能的生命周期
    console.log("shouldComponentUpdate");
    return true // 写了此钩子必须要写返回值(true会执行render更新值,false则不会执行render)
}
// 4. componentWillUpdate 组件将要更新的钩子(走render更新)
componentWillUpdate(){
    console.log("componentWillUpdate");
}
// 5. componentDidUpdate 组件更新完成的钩子
componentDidUpdate(){
    console.log("componentDidUpdate");
}

补充:父子组件,在子组件的一个钩子

// componentWillReceiveProps 组件将要接收新的props的钩子
//注意:第一次传的不算
 componentWillReceiveProps(props){ // 参数props是接收到的数据
   console.log("componentWillReceiveProps",props);
 }
强制更新
// 强制更新的回调(尽量避免使用)
 force=()=>{
     this.forceUpdate()
 }

 // 1. 初始化展示、状态更新之后
 render(){
     return <div>
             <button onClick={this.force}>不该状态,强制更新</button>
         </div>
 }

3. 卸载(旧)

组件卸载或者销毁前调用

// 组件即将卸载
componentWillUnmount(){
    console.log("componentWillUnmount")
}

// 卸载组件的回调
del=()=>{
    // 7.unmountComponentAtNode 卸载组件
    ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}
render(){
    return <div>
            <button onClick={this.del}>删除组件</button>
        </div>
}

新版

废弃3个钩子,增加两个钩子(并不常用)
在这里插入图片描述

1. getDerivedStateFromProps

需使用 static 修饰
需返回一个对象更新 state 或返回 null
适用于如下情况:state 的值任何时候都取决于 props

// 1.2 在render函数执行前调用,只要有render函数执行,都会调用该函数。
// 该函数有两个参数,第一个是props参数,第二个是state
// 如果有返回值,会把返回值作为state状态。没有返值或者返回false,则是使用原始state
static getDerivedStateFromProps(props,state){
    console.log("getDerivedStateFromProps");
    console.log(nextProps,state);
}

2. getSnapshotBeforeUpdate

在组件更新之前获取快照
得组件能在发生更改之前从 DOM 中捕获一些信息(如滚动位置)
返回值将作为参数传递给 componentDidUpdate()

// 更新前调用
getSnapshotBeforeUpdate(){
    console.log("getSnapshotBeforeUpdate");
    return "shot"
}
//  componentDidUpdate 组件更新完成的钩子
componentDidUpdate(preProps,preState,snapshotValue){
    console.log("componentDidUpdate",preProps,preState,snapshotValue);
}

8. 组件的核心属性 refs

refs可以收集带有ref属性的元素原生对象

class App extends React.Component{
    componentDidMount(){ // 第一次执行完render函数触发
        let h2 = this.refs.h2 // 获取原生dom对象
        h2.innerText = "hello" // 还可以修改里面的值
        console.log(h2);

        let user = this.refs.user // 获取自定义组件对象
        console.log(user);
    }

    render(){
        return <div>
                <h2 ref="h2">App</h2>
                <User ref="user" />
            </div>
    }
}

9. form 表单数据

利用 onChange 事件

class Form extends React.Component{
  state = {
      name:"",
      text:"",
      gender:"female", // 默认值
      like:["run","movie"], //复选框的值放入数组
  }

// 获取多选框的值需要自己收集到数组
   handleCheckbox = e=>{
       console.log(e.target.value,e.target.checked);
       const { value,checked } = e.target
       const { like } = this.state
       if (checked){ // 如果是选择,则添加到数组
           like.push(value)
       } else { // 如果是取消,则从数组删除该元素
           like.forEach((item,index)=>{
               if(item === value){
                   like.splice(index,1)
                   return
               }
           })
       }
       this.setState({like:[...like]})
   }

  render(){
      const { name,text,gender,like } = this.state
      return <form action="">
              {/* 简单的双向绑定 */}
                  <p>姓名:<input type="text" value={name} onChange={(e)=>this.setState({name:e.target.value})} /></p>
                  <p>姓名:{name}</p>

                  <p>简介:<textarea value={text} onChange={e=>this.setState({text:e.target.value})} name=""></textarea></p>
                  <p>简介内容:{text}</p>

              {/* 单选框 */}
                  <p> 性别:
                      <input type="radio" checked={gender==="male"} name="gender" value="male" onChange={e=>{this.setState({gender:e.target.value})}} /><label></label>
                      <input type="radio" checked={gender==="female"} name="gender" value="female" onChange={e=>{this.setState({gender:e.target.value})}} /><label></label>
                  </p>
                  <p>性别:{gender}</p>

              {/* 复选框 */}
                  <p> 爱好:
                      <input type="checkbox" value="movie"  checked={like.includes("movie")} name="like" onChange={this.handleCheckbox} /><label>电影</label>
                      <input type="checkbox" value="run"  checked={like.includes("run")} name="like" onChange={this.handleCheckbox} /><label>跑步</label>
                      <input type="checkbox" value="dance" checked={like.includes("dance")} name="like" onChange={this.handleCheckbox} /><label>跳舞</label>
                  </p>
                  <p>爱好:{like}</p>
              </form>
  }
}

高阶函数写法:

class Form extends React.Component{
   state = {
       name:"",
       password:"",
   }
// 存表单类型
   handleFormData= (dataType)=>{
       return (e)=>{
           console.log(dataType,e.target.value);
           this.setState({ // []中可以直接传递一个变量名,这样就可以读出里面的变量值
               [dataType]:e.target.value
           })
       }
   }
     render(){
         const { name,password } = this.state
         return <form action="">
                     <p>姓名:<input type="text" onChange={this.handleFormData("name")} /></p>
                     <p>姓名:{name}</p>
                     <p>密码:<input type="password" onChange={this.handleFormData("password")} name="" /></p>
                     <p>密码:{password}</p>
                 </form>
     }
 }

ReactDOM.render(<Form/>,document.querySelector("#root"))

10. 虚拟 DOM 与 Diff 算法

key 的作用:

key 是虚拟 DOM 对象的标识,可提高页面更新渲染的效率。
状态中的数据发生变化时,React 会根据新数据生成新的虚拟 DOM ,接着对新旧虚拟 DOM 进行 Diff 比较,规则如下:

  • 旧虚拟 DOM 找到和新虚拟 DOM 相同的 key:
    .1. 若内容没变直接复用真实 DOM
    .2. 若内容改变,则生成新的真实 DOM ,替换页面中之前的真实 DOM
  • 旧虚拟 DOM 未找到和新虚拟 DOM 相同的 key:
    根据数据创建新的真实 DOM ,直接渲染到页面

使用 index 作为 key 可能引发的问题:

若对数据进行逆序添加逆序删除破坏顺序的操作,会进行没有必要的真实 DOM 更新。界面效果没问题,但效率低下。
如果结构中包含输入类的 DOM(如 input 输入框) ,则会产生错误的 DOM 更新。
若不存在对数据逆序添加、逆序删除等破坏顺序的操作,则没有问题。

四、脚手架

1. 安装使用

使用传统的方式:
全局安装: npm install create-react-app -g
切换到创建文件的目录下:create-react-app 文件名

使用vite(比较快):
在安装vite的前提下
npm create vite 文件名

2. Context组件 (给任意后代传值)

Context组件 提供了一个无需为每层组件添加props,就能在组件树之间进行数据传递(可以给任意后代组件传递属性)

注意: 如果有多个祖先组件都传递值,只会接收最近那个组件传递的值

由于props属性只能是父子传递,如果父组件需要传递属性到后代组件,只能一层层传递,非常麻烦

方式一:利用contextType挂载

挂载:组件名.contextType = ColorContext
取值:this.context

import { Component,createContext } from "react";
// 1. 使用createContext方法定义一个Context组件
const DateContext = createContext()

// Demo 子组件
class Demo extends Component {
  render(){
    return <div>
        获取App的值:{this.context}
    </div>
  }
}
// 3.把Context对象利用contextType挂载到组件上面,用this.context获取Provider组件的value值
Demo.contextType = DateContext // 必须放在组件定义后

// App父组件
export default class App extends Component {
  state = {
    name:"Mario"
  }
  render(){
    // 2. Context组件中有一个Provider的组件,该组件有一个value属性,该组件可以
    // 把value值传递到包裹的任意后端组件中
    return (
      // Demo组件(或Demo的后代组件)都在Provider组件内部,可以使用value属性的值
        <DateContext.Provider value={this.state.name}>
          <div>
            <h1>App!!!</h1>
            <Demo />
          </div>
        </DateContext.Provider>
    )        
  }
}

方式二:利用 Context.Consumer 组件

import { Component,createContext } from "react";

// 1. 使用createContext方法定义一个Context组件
const DateContext = createContext()

class Demo extends Component {
  render(){
    //3. Context.Consumer 组件内部是一个函数,是一个消费者,
    // 函数的默认参数是Provider 的value值
    return (
        <div>
          <DateContext.Consumer>
            {
              (e)=>{
                console.log(e); // 这个e就是传递的值
                return <h3>获取App的值:{e}</h3>
              }
            }
          </DateContext.Consumer>
        </div>
    )
  }
}

export default class App extends Component {
  state = {
    name:"Mario"
  }
  render(){
    // 2. Context组件中有一个Provider的组件,是一个提供者,该组件有一个value属性,该组件可以
    // 把value值传递到包裹的任意后端组件中
    return (
      // Demo组件(或Demo的后代组件)都在Provider组件内部,可以使用value属性的值
        <DateContext.Provider value={this.state.name}>
          <div>
            <h1>App!!!</h1>
            <Demo />
          </div>
        </DateContext.Provider>
    )        
  }
}

切换主体样式案例(类组件)

利用style样式控制颜色
App.jsx

import { Component,createContext } from "react";
import "./App.css"

const ColorContext = createContext()

class Text2 extends Component {
  render(){
    return (
      // 函数方式
      <div>
        <ColorContext.Consumer>
          {e=><h2 className={e+"-con"}>这是Text2的内容...</h2>}
        </ColorContext.Consumer>
      </div>
    )
  }
}

class Text1 extends Component {
  render(){
    return (
      <div>
        <h2 className= {this.context+"-con"}>这是Text1的内容...</h2>
        <Text2 />
      </div>
    )
  }
}
Text1.contextType = ColorContext // 挂载方式

export default class App extends Component {
  state = {
    theme:"pink" // 默认色
  }
  changeColor = (e)=>{
    this.setState({
      theme:e.target.value
    })
  }

  render(){
    const { theme } = this.state
    return (
      <div className={theme}>
        <header className="head">
          主题色:
          <select value={theme} onChange={this.changeColor}>
            <option value="pink">粉色</option>
            <option value="green">绿色</option>
            <option value="gray">灰色</option>
          </select>
        </header>

        <ColorContext.Provider value={theme}>
          <Text1 />
        </ColorContext.Provider>
      </div>
    )
  }
}

App.css

.pink .head {
  background-color: pink;
  padding: 20px;
}
.green .head {
  background-color: #096;
  padding: 20px;
}
.gray .head {
  background-color: #ccc;
  padding: 20px;
}

.pink-con {
  border: 1px solid pink;
  color: pink;
}
.green-con {
  border: 1px solid #096;
  color: #096;
}
.gray-con {
  border: 1px solid #ccc;
  color: #ccc;
}

五、函数组件

class类组件在react16之前的版本使用较多,16版本后官方推荐使用函数组件
函数组件内部没有类组件的属性和方法,也没有生命周期钩子,就是一个单纯函数,依赖较少,运行效率比较高

注意:函数组件中没有this

hook 函数:

函数组件需要使用react的内置功能,如果state等,需要引入对应的hook函数(钩子函数)
react官方把react常用的功能做成了钩子函数(hook),需要什么功能就引入对应的hook

1. props 父=>子

函数组件默认参数就是props属性对象,父组件传递的props属性都放在props对象里面

function Demo(props){
  return <h2>
          <p>{props.name}--{props.age}</p>
        </h2>
}

export default function App(){
  return <div>
          <Demo name="RRR" age="19" />
        </div>
}

// 直接解构
function Aritical({num,other}){
  return (
    <>
      <h4>{num}</h4>
      <h4>{other.content}</h4>
    </>
  )
}
function App() {
   let appDate = {
    num:100,
    other:{
      id:"001",
      content:"SEND All DATA"
    } 
  }
  return (
    <div><Aritical {...appDate} /></div>   
  )
}
export default App

props 子=>父

import { useState } from "react"
function Aritical({ handleShow} ){
  const [show,setShow] = useState(false)
  function handleChange(){
    setShow(!show)
    handleShow(show)
  }

  return (
    <>
      <button onClick={handleChange}>按钮</button>
      <h4 style={{display:show?'block':'none'}}>显示</h4>
    </>
  )
}
function App() {
  function receiveShow(show){
    console.log(show);
  }
  return (
    <>
      <Aritical handleShow={ receiveShow } />
    </> 
  )
}
export default App

2. useState 只能定义一个state

可以在函数组件内部使用state。userState返回一个数组
第一个参数是state状态,第二个参数是设置state状态的函数。调用第二个参数函数,可以修改state,state修改,函数会重新执行。但是state状态会被保留。

import { useState } from "react"

function Demo(){
// 第一个参数是状态名称,第二个参数函数必需是参数名称前面加set前缀。
// 可以声明多个state
  const [count,setCount] = useState(0) // 设置count的默认值为0
  const [age,setAge] = useState(20)
  return <h2>
            <p>值:{count}</p>
            <button onClick={()=>{setCount(count+1)}}>count+1</button>
            <p>年龄:{age}</p>
            <button onClick={()=>setAge(age+1)}>年龄+1</button>
        </h2>
}

export default function App(){
  return <div>
          <Demo />
        </div>
}

定义对象、数组形式

import { Fragment, useState } from "react"

function App() {
// 1.对象结构
  const [data,setData] = useState({ 
    name:"zs",
    age:20
  })
  function handleData(){
    // 注意:修改某个属性也要把所有属性写全,防止新对象替换掉之前的旧对象
    setData({ 
      ...data, // 使用展开运算符展开所有的属性
      age:22
    })
  }

// 2.数组结构
  const [list,setList] = useState([
    {id:1,name:"a1"},
    {id:2,name:"a2"},
    {id:3,name:"a3"},
  ])
  const listDate = list.map(item => (
    // Fragment 标签相当于vue中的templete标签,不会渲染成DOM元素
    <Fragment key={item.id}>
      <li>{item.name}</li>
      <li>-----------</li>
    </Fragment>  
  ))

  function handleList(){
    // setList([
    //   ...list,
    //   {id:4,name:"a4"}
    // ])
    setList (
      list.filter(item=>item.id !== 2)
    )
  }
  return (
    <>
      <button onClick={handleData}>点击修改Data</button>
      <div>{data.name}--{data.age}</div>

      <ul>
      {listDate}
      </ul>
      <button onClick={handleList}>按钮</button>
    </>   
  )
}
export default App

3. useReducer 定义多个state

useReducer(function,initState) 可以定义多个state,是useState的替代方案(useState只能定义一个状态)

  • 第一个参数function(state,action)函数又有两个参数,第一个参数是state状态action是传递参数
  • 第二个参数initState初始状态
import { useReducer } from "react"

// 第一个参数 函数提出来
function reducer(state,action){
  if (action){
    // 合并state对象和action对象,action中的值会覆盖掉state相同的值,返回新的state
    return { ...state, ...action };
  }
  return state // 函数必需要有一个返回值,返回值作为state的值
}

// 第二个参数 初始值提出来
const initState = {
  name:"ZV",age:28
}

function Demo(){
// useReducer返回一个state和dispatch函数,dispatch函数就是第一个参数function函数,传值则是action参数
  const [state,dispatch] = useReducer(reducer,initState)
  const changeState = ()=>{
    dispatch({name:"Anna",age:19})
  }
  return <h2>
            <p>name:{state.name}</p>
            <p>age:{state.age}</p>
            <button onClick={changeState}>修改值</button>
        </h2>
}

export default function App(){
  return <div>
          <Demo />
        </div>
}

4. useEffect 可监听数据变化/模拟生命周期钩子

Effect Hook 让我们能在函数式组件中执行副作用操作(就是模拟生命周期钩子
副作用操作:发送 Ajax 请求、定时器、手动更改真实 DOM
Effect Hook 可以模拟三个钩子:componentDidMountcomponentDidUpdatecomponentWillUnmount
React.useEffect 第一个参数 return 的函数相当于 componentWillUnmount ,若有多个会按顺序执行

useEffect(function,[params]):第一个参数function是函数,第二个参数是监听数据的数组

import { useEffect } from "react";
// 1. 模拟componentDidMount生命周期(组件第一次渲染完成执行函数)
/* a.第二个参数如果是一个常量,则内部的函数只会执行一次。
b.第二个参数数组为空,表示不监听任何状态的更新,因此只有页面首次渲染会执行输出*/
   useEffect(()=>{
     console.log("第一次渲染");
   },[1])

// 2. 模拟全部状态 componentDidUpdate
// 若第二个参数不写,表示监听所有状态的更新
// 数组里面监听的值发生改变,函数会执行。
   useEffect(()=>{
      console.log('All DidUpdate')
	  return () => {
	    console.log('WillUnmount 2')
	  }
   })

// 3. 模拟部分状态 componentDidUpdate
// 第二个参数数组写上状态,表示只监听这些状态的更新
	React.useEffect(() => {
	  console.log('Part DidUpdate')
	  return () => {
	    console.log('WillUnmount 3')
	  }
	}, [count, name])

5. useContext 给任意后代组件传值

常用于祖父组件与子孙组件。实际开发一般不用,一般用 React-Redux

import { useState,createContext,useContext } from "react";
// 1.创建Context容器对象:
const XxxContext = createContext()

// 2. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<XxxContext.Provider value={数据}>
  子组件
</XxxContext.Provider>

// 3. 后代组件使用useContext读取数据
// 获取Provider value属性的传值,需要把创建的XxxContext组件传入hook
const value = useContext(XxxContext)

例:修改count值

import { useState,createContext,useContext } from "react";
// 1.创建Context容器对象:
const CountContext = createContext()

function Demo(){
  // 获取Provider value属性的传值,需要把对应的Context组件传入hook
  const countValue = useContext(CountContext)
  return (
    <h3>后代组件展示值:{countValue}</h3>
  )
}

export default function App (){
  const [count,setCount] = useState(1)
    return (
      <CountContext.Provider value={count}>
        <div>
          <Demo/>
          <button onClick={()=>setCount(count+1)}>祖组件修改值</button>
        </div>
      </CountContext.Provider>
    )
}

5. useRef 获取原生dom对象(html标签)

注意:组件的ref属性不能自动绑定到ref对象

import { useEffect,useRef } from "react";
export default function App (){
    const pNode = useRef(null)
    useEffect(()=>{
      console.log(pNode.current); //pNode.current获取原生DOM
      pNode.current.innerHTML = "Change p" // 可以修改里面的内容
    },[])
    return (
        <div>
          <p ref={pNode}>这是p标签</p>
        </div>
    )
}

6. useCallback 缓存一个函数

useCallback(function,[params]),缓存一个函数,监听的params参数发生改变,才会更新缓存。
和useEffect有点类似,主要用于缓存函数,用于提升性能
实际开发新手建议少用,对react非常熟练后再看情况使用

import { useState,useCallback } from "react";
export default function App (){
    const [count,setCount] = useState(1)

    const addCount = useCallback(()=>{
      setCount(count+1) //会一直缓存当前值
    },[count])
    // 监听一个常量,则不会更新缓存。
    // 如果监听的值发生改变,则会更新缓存函数

    return (
        <div>
          <h3>Count:{count}</h3>
          <button onClick={addCount}>增加</button>
        </div>
    )
}

7. useMemo 缓存一个值

useMemo(function,[params])缓存一个,和useCallback类似。有点类似vue的计算属性。
监听的params数据改变,才会触发缓存值更新

import { useState,useMemo } from "react";

export default function App (){
    const [count,setCount] = useState(1)
    const getCount = useMemo(()=>{
      // 函数的逻辑代表都会缓存起来,值不会变化
      return "¥"+ count + "元"
    },[count])
    // 监听一个常量,则不会更新缓存。
    // 如果监听的值发生改变,则会更新缓存函数

    return (
        <div>
          <h3>Memo:{getCount}</h3>
          <h3>Count:{count}</h3>
          <button onClick={()=>setCount(count+1)}>增加</button>
        </div>
    )
}

8. 高阶组件(HOC) 返回新组件

一个函数参数为一个组件返回值是一个新组件(返回的组件就叫高阶组件)
返回高阶组件的函数就叫高阶函数。
高阶组件主要解决组件逻辑复用的一种方式,HOC是一种设计模式,并不是react的api

// 定义一个函数组件
function Money(props){
  return <h3>{props.money}</h3>
}

function createCom(Com){
  return function NewCom(props){
    return (
      <div>
        <p>...</p>
        {/* 函数传递什么组件,Com就是什么组件 */}
        <Com {...props} />
      </div>
    )
  }
}
// NewCom1是createCom返回的组件,是一个高阶组件
const NewCom1 = createCom(Money)

export default function App (){
    return (
        <div>
          <NewCom1 money={50}/>
        </div>
    )
}

9.自定义hook

自定义一个hook函数,函数名称必需以use开头,自定义hook里面可以使用其他hook函数
例:判断分数状态

function useScore(initVal){
  const [status, setStatus] = useState(initVal);
  const setScore = function(val){
    if (val > 90) {
      setStatus("优秀");
    } else if (val > 75) {
      setStatus("优良");
    } else if (val > 60) {
      setStatus("及格");
    } else {
      setStatus("不及格");
    }
  }
  // 把分数对应的状态,和判断分数状态的函数返回
  return [status,setScore]
}

export default function App (){
  const [val,setVal] = useState(0)
  const [status,setScore] = useScore("及格");
    return (
      <div>
        <p>
          <input type="text" onChange={(e)=>setVal(e.target.value)} />
        </p>
        <p>状态:{status}</p>
        <button onClick={()=>setScore(val)}>获取状态</button>
      </div>
    )
}

10. jsx插槽

  // 会接收一个参数 children 为组件中包裹的内容
function Aritical({ children,title,footer=<div>默认值</div> }){
  return (
    <>
      <h3>{title}</h3>
      <ul>{children}</ul>
      {footer}
    </>
  )
}


function App() {
  return (
    <>
      <Aritical title="标题1" footer={<div>底部1</div>} >
        <li>内容1</li>
        <li>内容2</li>
        <li>内容3</li>
      </Aritical>
    </> 
  )
}
export default App

六、脚手架配置代理 Proxy

单个代理

package.json 文件中进行配置:

"proxy": "http://localhost:5000"

优点:配置简单,前端请求资源可不加前缀
缺点:不能配置多个代理
工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器

多个代理

在 src 目录下创建代理配置文件 setupProxy.js ,进行配置:

const proxy = require('http-proxy-middleware')

module.exports = function(app){
    app.use(
        //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000),方便区分和限制
        proxy('/api1',{ 
            target:'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
            changeOrigin:true, //控制服务器接收到的请求头中host字段的值
            /*
            changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
            changeOrigin设置为false时,服务器收到的请求头中的host为原host:localhost:3000
            changeOrigin默认值为false,但一般将changeOrigin改为true,防止后台做一些限制不能正常拿数据
            */
            pathRewrite:{"^/api1":""},//去除请求前缀,保证交给后台服务器的是后台中正常请求地址(必须配置)
        }),
        proxy('/api2',{
            target:'http://localhost:5001',
            changeOrigin:true,
            pathRewrite:{"^/api2":""},
        }),
    )
}

fetch 发送请求

除了xhr封装的一些请求方法,比如axios…(需引入/下载)
fetch不用下载和引入,浏览器自带的网络请求方法,比原生的xhr好用一点。关注分离的设计思想

search = async ()=>{

   try {
     // 先看服务器是否联系得上
     //只会返回正确的Promise对象
     const response = await fetch('https://...')
     // 通过json方法 再获取里面数据
     const data = await response.json()
     console.log(data)
   } catch (error) { // 出错统一返回错误信息
     console.log('请求出错', error)
   }
   
}

七、消息订阅-发布机制(兄弟间通信)

即 React 中兄弟组件或任意组件之间的通信方式。
先订阅,再发布(隔空对话)
要在 componentWillUnmount 钩子中取消订阅
下载安装 PubSubJS :npm install pubsub-js --save

import PubSub from 'pubsub-js' // 引入

// 1.订阅消息 subscribe
var token = PubSub.subscribe('topic', (msg, data) => {
  console.log(data)
})

// 2.发布消息 publish
PubSub.publish('topic', 'hello reactXXXX')

// 3.取消订阅
componentWillUnmount(){
   PubSub.unsubscribe(token)
}

八、路由

安装:npm i react-router-dom

1. BrowserRouter

BrowserRouter路径都是 …/home
Routes是创建路由管理器,
Route是创建路由对象

import { BrowserRouter,Route,Routes,Link } from "react-router-dom"
import Home from "./pages/home"
import List from "./pages/list"

export default function App (){
    return (
        <BrowserRouter>
          <div>
            {/* Link 跳转路由组件,to是路由path的路径 */}
            <Link to="/home">home</Link> <br />
            <Link to="/list">list</Link>
          </div>
          {/* Routes 渲染路由的组件,只会渲染一个符合条件的Route */}
          <Routes>
            {/* Route 配置路由的组件,path是路由路径,element是路由渲染的组件 */}
            <Route path="/home" element={<Home />} />
            <Route path="/list" element={<List />} />
          </Routes>
        </BrowserRouter>
    )
}

2. HashRouter

HashRouter路径都是 …#/home

export default function App (){
    return (
        <HashRouter>
          <div>
            {/* Link 跳转路由组件,to是路由path的路径 */}
            <Link to="/home">home</Link> <br />
            <Link to="/list">list</Link>
          </div>
          {/* Routes 渲染路由的组件,只会渲染一个符合条件的Route */}
          <Routes>
            {/* Route 配置路由的组件,path是路由路径,element是路由渲染的组件 */}
            <Route path="/home" element={<Home />} />
            <Route path="/list" element={<List />} />
          </Routes>
        </HashRouter>
    )
}

3. 获取动态参数 useParams

export default function App (){
    return (
        <HashRouter>
          <div>
          {/* 路由格式要正确 */}
            <Link to="/home/1/122">home</Link> <br />
          </div>
          <Routes>
          {/* 动态路由参数,不支持正则匹配,可以指定多个动态路由参数 */}
            <Route path="/home/:id/:num" element={<Home />} />
          </Routes>
        </HashRouter>
    )
}

Home.jsx

import { useParams } from 'react-router-dom'

export default function Home() {
// 获取动态路由参数,需要使用 useParams hook函数,返回一个动态路由参数对象
    const params = useParams()
    console.log(params);// {id: '1', num: '122'}
  return (
    <div>
      <h2>Home</h2>
    </div>
  )
}

4. 嵌套路由

import { HashRouter,Route,Routes } from "react-router-dom"
import Layout from "./pages/layout"
import Home from "./pages/home"
import List from "./pages/list"

export default function App (){
    return (
        <HashRouter>
          <Routes>
            {/* 嵌套路由 */}
            <Route path="/layout" element={<Layout />}>
              {/* 嵌套下面的路由的路径不要带 /  */}
                <Route path="home" element={<Home/>} />
                <Route path="list" element={<List/>} />
            </Route>
            {/* *匹配全部路由,一般放在最后,匹配所有路由 */}
            <Route path="*" element={<NotFound/>} />
          </Routes>
        </HashRouter>
    )
}

Outlet

import { Link,Outlet } from "react-router-dom"

export default function Layout() {
  return (
    <div>
      <h2>Layout...</h2>
      <ul>
        {/* 这里路径要写完整 */}
        <li><Link to="/layout/home">首页</Link></li>
        <li><Link to="/layout/list">列表</Link></li>
      </ul>
      <main>
        {/* Outlet表示嵌套路由组件显示的位置 */}
        <Outlet />
      </main>
    </div>
  )
}

5. 跳转路由

import { useNavigate } from "react-router-dom"

export default function Home() {
  // 使用 useNavigate 返回一个路由对象
  const navigator = useNavigate()
  const goPage = ()=>{
    // 编程式导航跳转路由
    navigator("/layout")
  }
  return (
    <div>
      <h2>Home...</h2>
      <button onClick={goPage}>跳转Layout页面</button>
    </div>
  )
}

6. 获取当前路由 useLocation

import { useLocation } from "react-router-dom"

  const location = useLocation()
  console.log(location);

九、redux状态管理

和vuex类似
安装:cnpm i @reduxjs/toolkit

1. 原始 redux

import { configureStore,combineReducers } from "@reduxjs/toolkit";
// 1. 创建reducer函数,返回一个state
// reducer 函数主要是处理state(初始化state,修改state)
// 需要给state赋值一个初始值
// action是actions触发reducer函数传递的参数
// 注意:state一定要定义初始值
function reducer(state={a=0},action){
// 一般reducer函数内部根据action的参数type做逻辑分支处理
	...
    console.log(state,action);
    return state
}
// 2. 使用configureStore方法创建store,需要传入reducer方法
// 注意:这里传入的是一个对象
const store = configureStore({reducer:reducer})
// 3. 监听store里面reducer函数返回的state变化,只要有变化就会触发subscript函数
store.subscribe(function(){
    // 获取全局状态的state
    const state = store.getState()
})
// 4. action触发reducer函数。dispatch就可触发reducer函数并传递参数
store.dispatch({type:"xxx",count:0})

export default store // 暴露store

combineReducers可以合并多个reducer,主要用于reducer模块化

import { configureStore,combineReducers } from "@reduxjs/toolkit";
// combineReducers可以合并多个reducer,主要用于reducer模块化
// 创建reducer1:
// 初始值
const initUserDate = {profile:""}
const reducer1 = function(state=initUserDate,action){
    const { type,data } = action
    // 分模块,type一般需要带上模块的名称以和其他模块区分
   ...
}

// 创建reducer2
const globalInit = {}
const reducer2 = function(state=globalInit,action){
    const { type,date } = action
    ...
}
const reducer = combineReducers({
    user:reducer1,
    global:reducer2
})
const store =  configureStore({reducer})
export default store

改变count

import { configureStore } from "@reduxjs/toolkit";
// 注意:state一定要定义初始值
function reducer(state={count:0},action){
    switch (action.type) {
        case "add":
            return {...state,count:state.count + action.count}
        default:
            break;
    }
    return state
}

// 注意:这里传入的是一个对象
const store = configureStore({reducer})
export default store // 暴露store
import store from "../store"
import { useEffect,useState } from "react"

export default function Home() {
  const [count,setCount] = useState(0)
// 第一次页面渲染完成就要修改值
  useEffect(()=>{
    const con = store.getState()
    setCount(con.count)
  },[1])
  
// 监听store里面的值,保证页面随之改变
  store.subscribe(()=>{
    const res = store.getState()
    setCount(res.count)
  })

// 操作count
  const addCount = ()=>{
    store.dispatch({type:"add",count:6})
  }

  return (
    <div>
      <p>count:{count}</p>
      <button onClick={addCount}>改变count</button>
    </div>
  )
}

2. 第三方redux(使用较多)

安装:npm i react-redux
react-redux将redux绑定到react里面,主要是把dispatch方法和state状态绑定到react组件的props属性
store的三大功能:dispatch,subscribe,getState都不需要手动来写了。react-redux帮我们做了这些,同时它提供了两个好用的Providerconnect

// Provider组件可以将store传递到包裹的组件里面
import { Provider } from "react-redux"
import store from "./store"

export default function App (){
    return (
      <Provider store={store}>
        ...
      </Provider>
    )
}

connect 接受当前组件作为参数,返回一个新组件,该组件会把全局store的state和dispatch放到组件的props属性上
两个参数:
mapStateToProps是一个函数,参数是store里面的state对象,返回一个对象并返回的对象放入合并到props属性
mapDispatchToProps把store里面的dispatch对象放入合并到props属性

// connect是一个高阶函数,有两个参数,并需要传递当前组件
import { connect } from "react-redux"

 function Layout(props) {
  console.log(props); // 可以拿到全局的state
  return (
    <div>
      <h2>Layout...</h2>
    </div>
  )
}

  const mapStateToProps = (state)=>{
    return state
  }
  const mapDispatchToProps = (dispatch)=>{
    // 把dispatch属性合并到props属性,它最终都会返回一个对象
    return {dispatch,}
  }

 // 它内部定义一个新组件Connect(容器组件)并将传入的组件(ui组件)作为Connect的子组件然后return出去
  export default connect(mapStateToProps,mapDispatchToProps)(Layout)
// 简写
export default connect((state)=>(state),dispatch=>({dispatch}))(Layout)

十、项目搭建

1. 准备

  1. 使用vite:npm create vite
  2. 安装必要包:npm i react-router-dom redux react-redux
  3. 安装UI库:npm install antd
  4. 安装sass:npm i sass-loader node-sass -D
    注意防止样式冲突问题:react中自带有样式命名规定xxx.module.scss

命名时:index.module.scss
引用sass样式:import styles from "./index.module.scss"

项目中:使用ESLint 关注代码质量,Prettier 关注代码风格
安装:cnpm install prettier eslint-config-prettier eslint-plugin-prettier -D

2. 大包 build

打包:npm run build

默认build打包是把所有组件的js打包到一个js,项目比较大,js文件大,加载就慢,用户体验不好
分片打包(组件的懒加载),每个组件的js单独打包,跳转到对应路由才加载相关组件 分片打包的使用lazy函数,可以实现js组件懒加载
lazy函数加载的组件必须包裹 Suspense 组件

import { lazy,Suspense } from "react"
import { HashRouter,Routes,Route } from "react-router-dom"

// import Login from "./pages/login"
// import Dashboard from "./pages/dashboard"
let Login = lazy(()=> import("./pages/login"))
let Dashboard = lazy(()=> import("./pages/dashboard"))

//定义一个高阶函数,传组件进来
function getLazyCom(Com){
   return <Suspense fallback={<div>Loading...</div>}>
       <Com />
   </Suspense>
 }
 
export default function Router() {
  return (
    <HashRouter>
      <Routes>
        <Route path="/login" element={getLazyCom(Login)} />
        <Route path="/" element={getLazyCom(Layout)}>
			<Route path="dashboard" element={getLazyCom(Dashboard)} />
		</Route> 
      </Routes>
    </HashRouter>
  )
}

React使用的UI库 antd

Ant Design(简称antd)
网址:https://ant.design/index-cn/

富文本编辑器wangeditor:https://www.wangeditor.com

一些 Echart 图网站:

  1. DataV:http://datav-react.jiaminghi.com/
  2. Apache ECharts:https://echarts.apache.org/zh/index.html
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值