重学react---基础篇

500 阅读12分钟

快速开始

npx create-react-app my-app       // 创建项目
cd my-app                         // 进入项目
npm start                         // 启动项目
npm run eject                     // 配置暴露项

入口文件->index.js

// 注意: 只要使用jsx语法就要引入react
// ReactDOM.render把组件挂载
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

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

// React负责逻辑控制,数据 -> VDOM
// ReactDom渲染实际DOM,VDOM -> DOM
// React使⽤用JSX来描述UI
// babel-loader把JSX 编译成相应的 JS 对象
// React.createElement再把这个JS对象构造成React需要的虚拟dom

入口文档定义

entry: [
// WebpackDevServer客户端,它实现开发时热更更新功能 
isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'), 
// 应⽤程序⼊口:src/index
paths.appIndexJs, 
// 在path文件可以找到 appIndexJs: resolveModule(resolveApp, 'src/index'),
].filter(Boolean),

webpack配置文件

webpack.config.js 是webpack配置⽂文件,开头的常量量声明可以看出cra能够⽀支持ts、sass及css模块化

// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
 

jsx语法

  1. 表达式{}的使⽤用
const name = "react study";
const jsx = <div>hello, {name}</div>;
  1. 函数表达式
const obj = {
  fistName: "曾",
  lastName: "小白"
};
function formatName(name) {
  return name.fistName + " " + name.lastName;
}
const jsx = <div>{formatName(user)}</div>;
  1. 对象表达式
const greet = <div>good</div>;
const jsx = <div>{greet}</div>;
  1. 条件语句
 
const show = true;
const greet = <div>good</div>;
const jsx = (
<div>
{show ? greet : "登录"} 
{show && greet}
</div>
);
  1. 数组表达式
const a = [0, 1, 2];
const jsx = (
<div>
<ul>
  {
    a.map(item => (
      <li key={item}>{item}</li>
      )
    )
  }
</ul> 
</div>
)
// diff时候,⾸首先⽐比较type,然后是key,所以同级同类型元素,key值必须得 唯⼀一
  1. 属性的使⽤用
import logo from "./logo.svg";
const jsx = (
  <div>
    // 属性:静态值⽤用双引号,动态值⽤用花括号;class、for等要特殊处理理。
    <img src={logo} style={{ width: 100 }} className="img" />
  </div>
);
  1. 模块化
// css模块化,创建index.module.css,index.js
import style from "./index.module.css";
<img className={style.logo} />

组件(实现一个定时器)

概念:类似于 JavaScript 函数。它接受任意的⼊入参(即 “props”),并返回⽤用于描述⻚页⾯面展示 内容的 React 元素。

组件有两种形式:class组件和function组件。

  1. class组件(class组件通常拥有状态和⽣生命周期,继承于Component,实现render⽅方法)
import React, {Component} from 'react';

class Clock extends Component {
  constructor(props) {
    super(props);
    this.state = {
      time: new Date()
    }

  }
  // 组件挂载完成之后执行
  componentDidMount() {
    this.timer = setInterval(() => {
      this.setState({
        time: new Date()
      })
    }, 1000)
  }

  // 组件卸载之前执行
  componentWillUnmount() {
    clearInterval(this.timer)
  }

  render() {
    const { time } = this.state
    return (
      <div>
        {time.toLocaleString()}
      </div>
    );
  }
}

export default Clock;
  1. function组件(函数组件通常⽆无状态,仅关注内容展示,返回渲染结果即可,但是从React16.8开始引⼊入了了hooks,函数组件也能够拥有状态,此时 useEffect Hook可以看作componentDidMount , componentDidUpdate 和 componentWillUnmount )
import React, {useState, useEffect} from 'react';

function Clock(props) {
  const [ time, setTime ] = useState(new Date());
  useEffect(() => {
    // 执行的毁掉函数相当于componentDidMount,
    // return执行的毁掉函数相当于componentWillUnmount,
    // 最后的是依赖项[],相当于componentDidUpdate
    const timer = setInterval(() => {
      setTime(new Date());
    }, 1000);
    return () => clearInterval(timer)
  },[])
  return (
    <div>
      {time.toLocaleString()}
    </div>
  );
}

export default Clock;

组件复合

有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。可以使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中

  1. 直接使用props.children渲染插入的全部节点
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

别的组件可以通过 JSX 嵌套,将任意组件作为子组件传递给它们。

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}
  1. 不使用 children,将所需内容传入 props,并使用相应的 prop组件。类似vue中的slot
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}
  1. 使用props中相应的prop值
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

redux

  1. 安装redux
 npm install redux --save
  1. 创建一个store---src/store/index.js
// 1. 需要⼀一个store来存储数据
// 2. store里面的reducer初始化state并定义state修改规则
// 3. dispatch派发一个action来提交对数据的修改
// 4.  action作为reduce的第二个参数传入,根据action的type,返回新的state

import { createStore } from 'redux'

// 定义state的初始化和规则, reducer是一个纯函数
function reducer(state=0, action) {
  switch (action.type) {
    case "ADD":
      return state+1;
    case "DEL":
      return state-1;
    default:
      return state
  }
}

const store=createStore(reducer)
export default store

  1. 最外层订阅 --- src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import './static/iconfont/iconfont.css'
import store from './store'

// ReactDOM.render(
//   <App />,
//   document.getElementById('root')
// );
const render = ()=>{
  ReactDOM.render(
    <App/>,
    document.querySelector('#root')
  )
}
render()

store.subscribe(render)

  1. 界面中使用
import React, {Component} from 'react';
import store from '../../store'

class ReduxPage extends Component {
  // 如果没有在index.js中订阅,则需要在这里订阅
  // componentDidMount() {
  //   // 订阅
  //   store.subscribe(() => {
  //     // 强制刷新
  //     this.forceUpdate()
  //   })
  // }

  render() {
    return (
      <div>
        ReduxPage
        <p>
          {store.getState()}
        </p>
        <button onClick={() => store.dispatch({type: 'ADD'})}>ADD</button>
        <button onClick={() => store.dispatch({type: 'DEL'})}>DEL</button>
      </div>
    );
  }
}

export default ReduxPage;
  1. 总结redux
1. createStore 创建store
2. reducer 初始化、修改状态函数 3. getState 获取状态值
4. dispatch 提交更更新
5. subscribe 变更更订阅

react-redux

  1. 安装
npm install react-redux --save
  1. 创建一个store---src/store/index.js跟redux一样
// react-redux提供两个api
// 1. provider为后代组件提供store
// 2. connect为组件提供数据和变更方法
import { createStore } from 'redux'

// 定义state的初始化和规则, reducer是一个纯函数
function reducer(state=0, action) {
  switch (action.type) {
    case "ADD":
      return state+1;
    case "DEL":
      return state-1;
    default:
      return state
  }
}

const store=createStore(reducer)
export default store
  1. 最外层引入--- src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import './static/iconfont/iconfont.css'
import store from './store'
import {Provider} from 'react-redux'

// 用react-redux提供的Provider方法包裹最外层app,并把store传递进去
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
  1. 页面使用(使用connect把页面和状态联系起来)
import React, {Component} from 'react';
import {connect} from "react-redux";

class ReactReduxPage extends Component {

  render() {
    const {num, add, del} = this.props;
    return (
      <div>
        ReactReduxPage
        <p>
          {num}
        </p>
        <button onClick={add}>ADD</button>
        <button onClick={del}>DEL</button>
      </div>
    );
  }
}

// //状态映射 mapStateToProps,把状态映射到props上
const mapStateToProps = state => {
  return {
    num: state
  }
}

// 派发事件映射,把事件映射到props上,默认是把所有事件映射到props
const mapDispachToProps = {
  add: () => {
    return {type: 'ADD'}
  },
  del: () => {
    return {type: 'DEL'}
  },
}

// connect接收两个参数,所以它其实是一个高阶组件
// 第一个参数里面mapStateToProps是一个函数返回state,mapDispachToProps是一个对象,返回方法
// 第二个参数是当前页面
export default connect(mapStateToProps, mapDispachToProps)(ReactReduxPage);

react-router

  1. 概念: react-router包含3个库,react-router、react-router-dom和react-router-native。实际使用中我们会根据实际需求安装,react-router-dom(在浏览器器中使⽤用)或react-router-native(在rn中使⽤用),react-router-dom和 react-router-native都依赖react-router,所以在安装时,react-router也会⾃自动安装。
  2. 安装
npm install --save react-router-dom
  1. 组件思想 react-router奉行一切皆组件的气象,路由器--Router,链接---Link,路由---Route,独占路由---Switch,重定向---Redirect都以组件形式存在。
  2. 使用
import React, {Component} from 'react';
import {BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";
import IndexPage from '../IndexPage'
import UserPage from '../UserPage'
import EmptyPage from '../EmptyPage'

// Router包裹的Link相当于a标签,to指向跳转路径
// Router包裹的Route,path是路径,component是路径匹配的页面
// exact 精确匹配
// EmptyPage写在所有路由的最后面,当前面匹配不上的时候,匹配EmptyPage
// Switch表示独占路由,只渲染一个
class RouterPage extends Component {
  render() {
    return (
      <div>
        <Router>
          <Link to='/'>首页</Link>
          <Link to='/user'>用户中心</Link>
          <Switch>
            <Route
              exact
              path='/'
              component={IndexPage}
            />
            <Route path='/user' component={UserPage} />
            <Route component={EmptyPage} />
          </Switch>
        </Router>
      </div>
    );
  }
}

export default RouterPage;
  1. Route渲染内容有三种⽅方式
// 1. Route渲染优先级:children>component>render。并且这三种⽅方式互斥,只能⽤用一种。
// 2. children:func,不不管location是否匹配,你都需要渲染⼀一些内容,这时可以用children。
// 3. component: component,只在当location匹配的时候渲染
// 4. render:func,只在当location匹配的时候渲染
<Route 
    exact
    path='/'
    component={IndexPage}
    children={()=> <div>children</div>}
    render={()=> <div>render</div>}
  />

PureComponent

  1. 实现性能优化,PureComponent内部实现了shouldComponentUpdate方法
  2. 内部的比较是一个浅比较, React.PureComponent 中以浅层对比 prop 和 state 的⽅式来实现了shouldComponentUpdate() 方法
  3. PureComponent如果有object形式的数据,还是会在数据改变的时候每次都重新渲染
  4. 写PureComponent必须是class类型的组件页面
import React, {Component,PureComponent} from 'react';

class PureComponentPage extends PureComponent {
  constructor(props){
    super(props);
    this.state={
      count: 0
    }
  }
  setCount=() => {
    this.setState({
      count: 10
    })
  }
  // PureComponent内部实现了shouldComponentUpdate方法
  // 如果不使用PureComponent则需要手动实现是否重新加载
  // shouldComponentUpdate(nextProps, nextState, nextContext) {
  //   return nextState.count!==this.state.count
  // }

  render() {
    console.log('render')
    const {count} = this.state
    return (
      <div>
        <button onClick={this.setCount}>{count}</button>
      </div>
    );
  }
}

export default PureComponentPage;

注意:

  1. React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层⽐比较。如果对象中 包含复杂的数据结构,则有可能因为⽆无法检查深层的差别,产⽣生错误的⽐比对结果。仅在你的 props 和 state 较为简单时,才使⽤用 React.PureComponent ,或者在深层数据结构发⽣生变化时 调⽤用 forceUpdate() 来确保组件被正确地更更新。
  2. React.PureComponent 中的 shouldComponentUpdate() 将跳过所有⼦子组件树的 prop 更更新。因此,请确保所有⼦子组件也都是“纯”的组件。

认识Hook

概念: 在编写函数组的时候使用,Hook 是一个特殊的函数,它可以让你“钩⼊” React 的特性。

  1. 使用useState Hook(使用useState进行状态管理)
import React, {useState,useEffect} from 'react';

export default function HookPage(props) {
  // 声明⼀一个叫 num 的 state 变量量,初始化为0
  // setNum接收新的state,然后将状态更改为 'newState' 并触发重新渲染
  const [num, setNum] = useState(0)
  return (
    <div>
      HookPage
      <p>{num}</p>
      <button onClick={()=>setNum(num+1)}>add</button>
    </div>
  )
}

  1. 使用 Effect Hook
// 接收2个参数,第一个参数是一个函数
// 函数体相当于componentDidMount,和componentDidUpdate,可以执行一些订阅操作
// 函数return返回一个清除函数,防止内存泄漏,相当于componentWillUnmount
// 第二个参数数组里的数据effect的执行条件,只有当数组里面的值改变的时候才会创建新的订阅

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

export default function HookPage(props) {
  const [time, setTime] = useState(new Date())
  useEffect(() => {
    const timer = setInterval(() => setTime(new Date()), 1000)
    return () => clearInterval(timer)
  }, [])

  return (
    <div>
      HookPage
      <p>{time.toLocaleTimeString()}</p>
    </div>
  )
}

自定义Hook与Hook使⽤用规则

  1. 概念: 自定义Hook是一个函数,名称以'use'开头,函数内部可以调用其他hook。目的是为了在组件之间重用一些状态
import React, {useState, useEffect} from 'react';

export default function HookPage(props) {
  return (
    <div>
      HookPage
      <p>{useClock().toLocaleTimeString()}</p>
    </div>
  )
}


function useClock() {
  const [time, setTime] = useState(new Date())
  useEffect(() => {
    const timer = setInterval(() => setTime(new Date()), 1000)
    return () => clearInterval(timer)
  }, [])
  return time
}

  1. 使用规则: 只能在函数最外层调⽤Hook。不要在循环、条件判断或者子函数中调⽤。只能在 React 的函数组件中和自定义Hook中调⽤用 Hook。

Hook API之useMemo与useCallback

  1. useMemo(() => fn, deps)
// 1. 接收2个参数,
// 2. 第一个参数是函数,
// 3. 第二个参数是数组作为依赖项,它仅会在依赖项改变的时候才会重新计算,有助于避免在每次渲染时都进⾏高开销的计算

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

export default function UseMemoPage(props) {
  const [num, setNum] = useState(0)
  const [value, setValue] = useState('')

  const expensive = useMemo(()=> {
    console.log('computed')
    let sum = 0;
    for(let i=0; i<num; i++) {
      sum += i;
    }
    return sum
    // 只有num改变的时候,当前函数才会重新执行
  },[num])
  return (
    <div>
      <h1>UseMemoPage</h1>
      <p>num: {num}</p>
      <p>expensive: {expensive}</p>
      <button onClick={() => setNum(num+1)}>add</button>
      <input value={value} onChange={(e) => setValue(e.target.value)}/>
    </div>
  )
}
  1. useCallback(fn, deps)
// 1. 接收2个参数,
// 2. 第一个参数是内联回调函数,
// 3. 第二个参数是数组作为依赖项,它仅会在依赖项改变的时候前面的回调函数才会重新更新,有助于避免在每次渲染时都进⾏高开销的计算
// 4. 当你把回调函数传递给经过优化(例如shouldComponentUpdate)的子组件,它将⾮常有⽤

import React, {useState, useCallback, PureComponent} from 'react';

export default function UseMemoPage(props) {
  const [num, setNum] = useState(0)

  const add = useCallback(() => {
    let sum = 0;
    for (let i = 0; i < num; i++) {
      sum += i;
    }
    return sum
    // 只有num改变的时候,当前函数才会重新执行
  }, [num])
  return (
    <div>
      <h1>UseMemoPage</h1>
      <p>num: {num}</p>
      <button onClick={() => setNum(num + 1)}>add</button>
      <Child addClick={add}/>
    </div>
  )
}

class Child extends PureComponent {
  render() {
    const {addClick} = this.props
    return (
      <div>
        <h1>ChildPage</h1>
        <button onClick={() => console.log('Child', addClick())}>childAdd</button>
      </div>
    )
  }
}

Antd基础配置项

yarn add antd
  1. 试⽤用 ant-design组件库
import React, { Component } from 'react'
import Button from 'antd/es/button'
import "antd/dist/antd.css"
class App extends Component {
  render() {
    return (
      <div className="App">
        <Button type="primary">Button</Button>
      </div>
) }
}
export default App
  1. 配置按需加载--1安装插件
// react-app-rewired 对 create-react-app 的默认配置进⾏行行⾃自定义
// customize-cra 为了支持 react-app-rewired@2.x 版本
// babel-plugin-import 是⼀一个⽤用于按需加载组件代码和样式的 babel 插件

yarn add react-app-rewired customize-cra babel-plugin-import

自定义主题

// ⾃自定义主题需要⽤用到 less 变量量覆盖功能
// 这里要注意less-loader的版本,最新的版本会报错,我用的5.0.0
yarn add less less-loader

根目录创建--- config-overrides.js


const {override, fixBabelImports, addLessLoader} = require("customize-cra");
module.exports = override(
  fixBabelImports("import", {
    // antd按需加载
    libraryName: "antd",
    libraryDirectory: "es",
    style: true
  }),
  addLessLoader({
    javascriptEnabled: true,
    modifyVars: {"@primary-color": "red"} // 修改主题色
  })
);

修改package.json

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