从Vue2.0到React的入门宝典

97 阅读5分钟

一、React起步

1.1 使用React脚手架初始化项目

// 1. 初始化项目
npx create-react-app 项目名
// 2. 启动项目
npm start

1.2 React项目结构介绍

image.png
public/                 公共资源
    index.html          首页html文件(必要)
    manifest.json       PWA应用元数据
    robots.txt          爬虫规则文件
 
src/                    项目功能代码
    index.js            项目入口文件(必要)
    App.js              项目的根组件
    App.test.js         App组件的测试文件
    reportWebVitals.js  页面性能监测
    setupTests.js       组件测试
    

1.3 Html与JSX

Vue推荐使用template模板,React推荐使用JSX语法。

JSXJavaScript XML的简写,表示JavaScript代码中写的XML(HTML)格式的代码

// JSX写法
<div className="App">
  <h1>Users List</h1>
  <ul>
    <li>张三</li>
    <li>李四</li>
  </ul>
</div>

使用步骤

  1. 使用JSX语法创建react元素
const title = <h1>Users List</h1>
  1. 使用React.DOM.render()方法渲染react元素到页面中
React.DOM.render(title, root)

JSX在脚手架中

image.png image.png

1.4 绑定Class和Style

(1) Vue

  • Vue中使用class绑定Class,使用style绑定Style;
  • 当使用变量绑定时,可以使用v-bind:class的简写:class以及v-bind:style的简写:style
  1. Vue中class和style的值为常量
<div class="title" style="margin: 10px"> hello world </div>
<style> 
    .title { padding: 5px }
</style>
  1. Vue中class和style的值为变量
<div :class="{isActive? active:''}" :style="{margin: activeMargin}"> hello world </div>
data:{
    activeMargin: "10px",
    isActive: true
}
<style> 
    .active { color: red }
</style>

(2) React

  • React中是用className来绑定 Class,用style来绑定 Style。
  • style用{{}}中括号并传入一个对象,同时该对象需要使用驼峰式命名(如 fontSize)
  1. React中class和style的值为常量
class HelloWorld extends React.Component { 
    render(
        return ( 
            <div 
                className="title" 
                style={{fontSize: '16px'}} 
            > 
                hello world 
            </div>
        ); 
    )
}
  1. React中class和style的值为变量
class HelloWorld extends React.Component { 
    state:{
        isActive: true,
        activeStyle: { margin: '10px' }
    }
    render(
        return ( 
            <div 
                className={`${this.state.isActive ? 'active' : ''}`} 
                style={activeStyle} 
            > 
                hello world 
            </div>
        ); 
    )
}

二、React事件

2.1 React事件处理

2.1.1 事件绑定

(1)在vue中使用v-on的方式绑定事件对象

<button v-on:click="onclick">点我</button>
// 使用v-bind的语法糖@
<button @click="onclick">点我</button>

(2)React

  • 语法: on + 事件名称 = { 事件处理程序 },如onClick = { () = {} }
  • React事件采用驼峰命名法,比如onChange, onAdd
// 类组件中
class Emit extends React.Component {
    handleClick() {
        console.log("事件已触发")
    }
    render() {
        return (
            <button onClick={ this.handleClick } />
        )
    }
}
// 函数组件中
function Emit {
    function handleClick() {
        console.log("事件已触发")
    }
    return (
        <button onClick={ this.handleClick } />
    )

}

2.2 事件对象

  • 可以通过事件处理程序的参数获取到事件对象
  • React中的实际对象叫合成事件(对象)
  • 合成事件:兼容所有浏览器
function Emit {
    function handleClick(e) {
        e.preventDefault()
        console.log("事件对象", e)
    }
    return (
        <a onClick={ this.handleClick } >点击但不会跳转</a>
    )

}

2.3 类组件和函数组件

  • 类组件也称为有状态组件;函数组件也称为无状态组件
  • 状态(state) 即数据
  • 函数组件没有自己的状态,只负责数据展示(静);
  • 类组件有自己的状态,能够更新UI

2.4 事件绑定this指向

2.4.1 箭头函数

class Emit extends React.Component {
    handleClick() {
        console.log("事件已触发")
    }
    render() {
        return (
            <button onClick={ () => this.handleClick() } />
        )
    }
}

2.4.2 bind()

使用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起

class Emit extends React.Component {
    constructor() {
        super();
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        console.log("事件已触发")
    }
    render() {
        return (
            <button onClick={ this.handleClick } ></button>
        )
    }
}

2.4.3 class的实例方法(常用)

利用箭头函数的形式的class实例方法

class Emit extends React.Component {
    handleClick = () => {
        console.log("事件已触发")
    }
    render() {
        return (
            <button onClick={ this.handleClick } ></button>
        )
    }
}

三、React组件

3.1 React中组件如何改变数据

使用setState修改组件中state的数据

class ChangeState extends React.Component {
    state: {
        title: "I am iron man"
    }
    handleClick = () => {
        this.setState(state => ({
            title: "I am spiderman"
        }))
    }
    render() {
        return (
            <button onClick={ this.handleClick } ></button>
        )
    }
}

this.setState()中可以传递一个函数或一个对象,建议传递一个函数(state,props) =>{},函数可以接受内部数据state和参数数据props作为参数,而且stateprops只读无法修改,每次调用this.setState时读取到的state和Props都是最新,特别适用多次调用this.setState修改同一个state的场景。最后函数返回一个对象,其内容为要修改的state。

3.2 常见的三种组件通讯场景

  • 父组件传子组件
  • 子组件传父组件
  • 兄弟组件间传值

3.2.1 父组件传子组件

  1. 父组件提供要传递的state数据
  2. 给子组件标签添加属性,值为state中的数据
  3. 子组件中通过props接收父组件中传递的数据
// 父组件
class Parent extends React.Component {
    state = { firstName: '张三' }
    render() {
        return (
        <div>
            数据传递给子组件:<Child name={this.state.lastName} /> 
        <div/>
        )
    }
}

// 子组件
function Child(props){
    return <div> 子组件接收到数据:{props.name} </div>
}

3.2.2 子组件传父组件

  1. 父组件提供一个回调函数(用于接收数据)
  2. 将该函数作为属性的值,传递给子组件
  3. 子组件通过props调用回调函数
  4. 将子组件的数据作为参数传递给回调函数
// 父组件
class Parent extends React.Component{ state ={ parentMsg : '' } 
    // 提供回调函数 接收数据 
    getChildMsg = (msg) =>{ 
        console.log("接受的信息", msg) 
    } 
    render(){ 
        return( 
           子组件:<Child getData={this.getChildMsg} /> 
        ) 
    } 
}

// 子组件
class Child extends React.Component{ 
state = { 
    childMsg :'子组件信息'
} 
handelClick = () =>{ 
    this.props.getChildMsg(this.state.childMsg)
} 
    render(){ 
        return( 
            子组件: <button onClick={this.handelClick}>按钮</button>
        ) 
    } 
}

3.3.3 兄弟组件通讯

  • 将 共享状态 提升到最近的公共父组件中,由公共父组件 管理这个状态 
  • 思想:状态提升
  • 公共父组件职责: 1.提供共享状态 2.提供操作共享状态的方法
  • 要通讯的子组件只需要通过props接收状态或操作状态的方法
class Parent extends React.Component {
    // 父组件提供共享状态
    state ={
      count: 0
    }
    // 父组件提供修改状态的方法
    countAdd = ()=>{
      this.setState({
        count: this.state.count+1
      })
    }
    render() {
      return (
        <div>
          <Child1 count={this.state.count}/>
          <Child2 Add={this.countAdd}/>
        </div>
      )
    }
  }
  const Child1 = (props) => {
    return <h1>计数器:{props.count}</h1>
  }
  const Child2 = (props) => {
    return <button onClick={()=>{props.countAdd()}}>+1</button>
  }

3.2 Context实现跨组件传递数据

(1)vue使用Provide/Inject实现跨组件数据传递

(2)React使用Context实现跨组件数据传递

const ThemeContext = React.createContext();
class App extends React.Component {
    render() {
        return (
          <ThemeContext.Provider value="dark">
              <Toolbar />
          </ThemeContext.Provider>
        );
    }
}

function Toolbar() {  
return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  render() {
    return <button>{ this.context }</button>;  
  }
}

3.3 props进阶

3.3.1 props的children属性

  • children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
const App = props => {
    return (
        <div>
            app组件标签子节点:{this.props.children}
        </div>
    )
} 
<App>我是子节点</App>

3.3.2 props校验

(1)vue实现props校验

// 父组件
<Children :arr="arr"/>
data(){
    return {
        arr: [0,1]
    }
}

// Children组件接收props
props: {
    arr: {
        type: Array //定义接收数据类型,实现校验功能
        require: true // 此项为必传
        default: () => [] //设置默认值
    }
}

(2)React实现props校验

  • 在创建组件的时候指定props的类型、格式,提供当遇到props导致的错误时给出错误提示

  • 使用步骤

    // 1. 安装包prop-types
    npm i props-types
    // 2. 导入prop-types
    import ProTypes from 'prop-types'
    function App(props) {
        return (
            <div>{props.arr}</div>
        )
    }
    
    // 3. 使用 组件名.propTypes = {}来给组件的props添加
    App.propTypes = {
        // 4. 校验规则通过PropTypes对象来指定
        // 约定arr属性为array类型,如果类型不对则会提示错误原因
        arr: PropTypes.array
    }
    
  • props校验约束规则

    1. 常见类型:string、number、object、array、func、bool
    2. React元素类型:element
    3. 必填项:类型.isRequired
    4. 特定结构的对象:shape({})
    
    optionalFunc: PropTypes.func,
    requiredFunc: PropTypes.func.isRequired,
    shapeObject: PropTypes.shape({
        name: PropTypes.string,
        age: PropTypes.number
    })
    
  • props的默认值

 function App(props) {
        return (
            <div>当前页码:{props.page}</div>
        )
    }
  // 设置默认值
  App.defaultProps = {
      page: 1
  }

3.4 组件生命周期

3.4.1 生命周期的三个阶段

(1) 创建时(挂载阶段)

  • 执行顺序:constructor -> render -> componentDidMount
钩子函数触发时机作用
constructor创建组件时,最先执行1.初始化state 2.为事件处理绑定this
render每次组件渲染都会触发渲染UI(但是不能调用setState)
componentDidMount组件挂载(完成DOM渲染)后1.发送网络请求 2.DOM操作

(2) 更新时(更新阶段)

  • 执行时机:1.setState() 2. forceUpdate() (主动更新方法) 3. 组件接收到新的props
  • 以上三者任意一种变化,组件就会重新渲染
  • 执行顺序:render -> componentDidUpdate()
钩子函数触发时机作用
render每次组件渲染都会触发渲染UI(与挂载阶段是同一个render)
componentDidUpdate组件更新(完成DOM渲染)后1.发送网络请求; 2.DOM操作; ps:如果要setState必须放在if条件中
// prevProps=更新前props
class Counter extends React.Component{
    componentDidUpdate(prevProps){
        // 比较更新前后的props是否相同,来决定是否更新渲染组件
        if(prevProps.count !== this.props.count){
            this.setState({})
        }
    }
}

(3) 卸载时(卸载阶段)

  • 执行时机:组件从页面中消失
钩子函数触发时机作用
componentWillUnmount组件卸载执行清理工作(比如:清理定时器)

3.5 组件复用

3.5.1 render props模式

  1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态 2.操作状态的方法)
  2. 将Mouse组件的状态作为props.children(state)方法的参数,暴露到组件外部
  3. 使用props.children()的返回值作为要渲染的数据
// Mouse组件
class Mouse extends React.Component {
    // ..省略state和操作方法state的方法
  render() {
    // 将Mouse组件的状态作为props.children(state)方法的参数,暴露到组件外部
    return this.props.children(this.state);
  }
}

// 父组件使用Mouse组件
<Mouse>{ showMouse }</Mouse>
// showMouse方法
const showMouse = (mouse) => {
// 使用props.children()的返回值作为要渲染的数据
  return (
    <p>
      鼠标的位置:{mouse.x},{mouse.y}
    </p>
  );
};

3.5.2 高阶组件

  1. 创建一个函数,名称约定以with开头
  2. 指定函数参数,参数应该以大写字母开头(作为要渲染都而组件)
// 1. 创建高阶组件
const withMouse = (WrappedComponent) => {
    class Mouse extends React.Component {
      // ..省略state和操作方法state的方法
      render() {
          // 2. 返回带有组件数据的传入组件(初始组件)
          // {...this.props}将初始组件的props一并返回
        return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>;
      }
    }
    // 设置displayName,便于调试时区分不同的组件
    Mouse.display = `WithMouse${WrappedComponent.displayName}`
    return Mouse
}

// 初始组件
const ShowMouse = (mouse) => {
  return (
    <p>
      鼠标的位置:{mouse.x},{mouse.y}
    </p>
  );
};
// 3. 将初始组件作为参数传入高阶组件,返回升级后的组件
const MousePosition = withMouse(ShowMouse);
// 4. 使用经高阶组件升级后的组件
<MousePosition></MousePosition>

四、React原理

4.1 setState()进阶

setState()

4.1.1 异步的setState()

  • setState()是异步更新数据的
  • 提示:使用该语法时,后面的setState()不要依赖前面的setState()
  • 可以多次调用setState(), 只会触发一次重新渲染

4.1.2 推荐setState()语法

使用setState((state, props) => {})语法,可以改变多次调用setState()而只渲染一次的情况。

state = {
    count: 0
}
// state表示最新的state,props表示最新的props
this.setState((state,props) => {
    return {
        count: state.count + 1
    }
})

4.1.3 setState()的第二个参数

  • 语法:setState(updater, [callback])
  • 适用场景:在状态更新后立即执行某个操作
this.setState(
    (state, props) => {},
    () => {console.log('在状态更新后立即执行这个操作')}
)

4.2 组件性能优化

4.2.1 减轻state

  • state只储存组件渲染相关的数据
  • 不做渲染的数据不要放在state中
  • 对于多个方法中用到的数据,应该放在this中

4.2.2 避免不必要的渲染

  • 使用shouldComponentUpdate(nextPorps, nextState),解决父组件更新引起的子组件更新问题
  • 通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行
class Hello extends React.Component {
   // newProps:最新的props,newState:最新的state
   shouldComponentUpdate(newProps, newState){
       // 根据条件,决定是否重新渲染
       // 如果最新的xxx与旧xxx不同则重新渲染,反之则不渲染
       return newState.xxx !== this.state.xxx
   }
    render() {...}
}

4.2.3 纯组件

2.3.1 纯组件使用

  • 纯组件:PureComponent与React.Component功能相似
  • 区别:PureComponent内部已集成shouldComponentUpdate钩子实现
  • 原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {
    render() {...}
}

2.3.2 纯组件注意点

  • 纯组件内部对比的是shallow compare(浅层对比)
  • 对于引用类型来说:只比较对象的引用(地址)是否相同,若仅仅只是修改对象中的值,那么纯组件将不会捕获到引用对象的更新,所以导致纯组件不会重新渲染
  • 当state或props中的属性值为引用类型时,应创建数据,不应直接修改原数据
// 纯组件中引用类型更新正确做法
const newObj = {...this.state.obj, number: 3}
this.setState({obj:newObj})

4.3 虚拟DOM和Diff算法

虚拟DOM

虚拟DOM本质是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)

image.png

执行过程

  1. 初次渲染时,React 会根据初始state(Model),创建一个虚拟DOM对象(树)
  2. 根据虚拟DOM生成真正的DOM,渲染到页面中。
  3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
  4. 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容。
  5. 最终,React 只将变化的内容更新(patch)到DOM 中,重新渲染到页面。

image.png

五、React路由

5.1 React路由介绍

5.1.1 路由的使用

// 1. 安装(此处写法基于6+)
npm i react-router-dom@6
// 2. 导入路由核心组件:Router/Route/Link
import { BrowserRouter as Router, Routes, Route,Link } from 'react-router-dom'
<Router>
    <div className='App'>
       // 3.使用Router组件包裹整个应用
       <Link to="/hello">Hello页面</Link>
       // 4. 使用Route组件配置路由规则和要展示的组件(路由出口)
       // react-router-dom v6版本Route外层需要Routes包裹
       <Routes>
            <Route path="/hello" element={<Hello />} />
       </Routes>
    </div>
</Router>

5.2 常用组件说明

  • Router组件:包裹整个应用,一个 React 应用只需要使用一次
  • 两种常用 Router:HashRouter 和 BrowserRouter
  • HashRouter:使用URL的哈希值实现(localhost:3000/#/first)(推荐)
  • BrowserRouter:使用H5的 history API 实现(localhost:3000/first)
  • Link组件:用于指定导航链接(a标签)
// to属性:浏览器地址栏中的pathname(location.pathname)
<Link to="/hello">Hello页面</Link>
  • Route组件:指定路由展示组件相关信息
// path属性:路由规则
// component属性: 展示的组件
// Route组件写在哪,渲染出来的组件就展示在哪
<Routes>
    <Route path="/hello" element={<Hello />} />
</Routes>

5.3 路由执行过程

  1. 点击Link组件(a标签),修改了浏览器地址栏中的url。
  2. React 路由监听到地址栏 url的变化。
  3. React路由内部遍历所有 Route 组件,使用路由规则( path)与 pathname 进行匹配。
  4. 当路由规则(path )能够匹配地址栏中的pathname 时,就展示该 Route 组件的内容。