【青训营】-React1-React基础

2,071 阅读9分钟

React环境搭建

环境安装

  1. 安装nodejs

  2. 安装react脚手架

    npm install create-react-app -g
    

创建项目

npx create-react-app my-app

cd my-app

npm start

项目结构

image-20210823092302892

react的加载流程

React的入口在index.js

index.js中会使用App.js作为组件加载

加载完成再渲染到HTML中

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';		/* 引入当前节点需要用到的css文件 */
import App from './App';	/* 引入需要用到的React组件 */
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />	
  </React.StrictMode>,			/* ReactDOM根节点的content,使用的使JSX语法 */
  document.getElementById('root') /* 指定ReactDOM的绑定对象 */
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

App.js

import logo from './logo.svg';	/* 引入图片路径 */
import './App.css';		/* 引入css文件 */

function App() {
    //使用 JSX语法描述组件的UI结构
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

React视图层

JSX语法

是基本js的扩展语法

jsx描述UI结构

const element = (
  <h1 className="greeting">
    Hello world!
  </h1>
)

会被在React内部转化成React 元素

//结构有简化
const element = {
  type:'h1',
  props:{
    className:'greeting',
    children:'Hello,world!'
  }
}

React元素渲染

ReactDOM.render(
  <React.StrictMode>
    <App />	
  </React.StrictMode>,			/* ReactDOM根节点的content,使用的使JSX语法 */
  document.getElementById('root') /* 指定ReactDOM的绑定对象 */
);

组件

React组件是在React元素之上的封装,是React应用 组成复用 的基本单元。

component = (props) => element

组件就像一个函数,接收props,然后返回一个元素

React组件必须 纯函数一样运行

纯函数

  1. 对于相同的入参,返回同样的结果
  2. 无副作用(副作用:当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响)

为什么说 呢?因为在组件运行时,需要根据一些网络请求进行改变。这就会很难避免的产生副作用,我们进行这样操作的时候需要按照React的规范进行。

组件可以分为两类:函数组件和类组件

函数组件

const ArticleList = (props) => {
  const { articles } = props;
  // 列表渲染,列表中的每一项要设置 key
  return articles.map((item) => {
    const { id, title } = item;
    return <div key={id}> {title}</div>;
  });
};

类组件

类组件中必须要重写render方法

class ArticleList extends React.Component {
  render() {
    const { articles } = this.props;
    return articles.map((item) => {
        const { id, title } = item;
        return <div key={id}> {title}</div>;
    });
  }
}

调用的时候传入

const articles = [
  { id: 1, title: 'Article 1' },
  { id: 2, title: 'Article 2' },
  { id: 3, title: 'Article 3' },
];

function App() {
  return <ArticleList articles={articles} />;
}

对于React来说,在渲染列表的时候需要指定key,如果不指定的话,就会报一个警告的错误

因为React为了性能考虑,会根据key的相同或者不同来判断是否需要元素复用,如果key相同,就会进行元素复用,来提高性能。

state

state的使用

前面我们进行的都是传入的固定的参数,得到的都是静态的元素,但是如果我们希望我们的React元素有一定的可变性,需要怎么做呢?

我们可以通过state可以让我们的元素具有动态性

需要的注意的是 state 只能在类组件中使用,函数组件中无法使用。

假如我们需要做一个实时显示的时间组件

我们就可以通过下面的例子创建

这里面我们定义了一个state

通过setState方法来更新里面的数据,这个方法更新完数据之后会重新渲染元素

实现时间的动态显示

import React, { Component } from 'react';

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

  componentDidMount() {
    this.timerID = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date(),
    });
  }

  render() {
    return (
      <div>
        <h1>Current Time: {this.state.date.toLocaleTimeString()}.</h1>
      </div>
    );
  }
}

组件执行流程

页面显示效果

显示效果

这里面使用到了setInterval()其实是访问了外部的api,其实就是我们说的副作用,但我们把副作用代码放在了生命周期函数中调用,只会调用一次,但是render函数中是不能有副作用代码的。

生命周期

由于state只能再类组件中使用,函数组件中无法使用,所以函数组件是无状态组件,类组件可以是有状态组件。

这里面用到了类组件中的生命周期函数

componentDidMount()是元素挂载完成后,调用的生命周期函数,只调用一次

componentWillUnmount()是元素被卸载时,调用的生命周期函数

组件的生命周期图

生命周期方法是类组件中定义的,对于函数组件中没有生命周期的概念

这里面主要看黑色加粗的这几个生命周期,这些是常用的生命周期方法,其他的的不常用,可以先不用关注

setState()

在进行状态更新时 需要使用 setState() API来进行更改,不能直接的进行访问state属性做修改

当我们通过setState()修改状态时,如果新的状态依赖当前的状态,不能给setState()传入一个对象的参数,而是需要传入一个函数,这个函数会接收两个参数,第一个参数是当前组件的state,第二个参数是组件最新的props,这么调用时React内部组件的setState()触发机制有关

setState()进行参数更改不是全部覆盖,而是局部更改

Props:父组件传递给子组件的属性,在子组件内部只读

State:组件内部自己维护的状态,内部可以通过setState()被修改

Form

受控组件

form中的值和值变化的管理逻辑都受React的控制

通过函数进行事件绑定

通过state进行值的更新

非受控组件

这里的input组件是通过ref进行的强绑定

在构造里创建ref

然后再渲染里进行绑定

组件的组合用法

刚刚我们创建的组件内部都是一个完整的整体,但是我们想要做到像普通的标签元素一样,内部可以再添加子元素的话,就涉及到了组件的组合用法。

从这个例子我们可以看出,内部的子元素都会通过props.children的方式传递到组件中。

这就相当于react在组件上打了一个洞,但有时候一个洞不够我们用怎么办呢?

其实这也很好理解,本身children就相当于React的props的一个固定属性,它对应着组件的子组件,那如果我们想要传入多个组件,就使用props的方式传入就行了

Hooks

hooks是React16.8以后提供的一种新的组件开发方式,其实就是一个函数,但这个函数有一个特殊的用途就是用来封装react中函数组件的状态管理逻辑的。

hook只能在函数组件中使用,而不能在类组件中使用

Stake Hook

这是一个最直接最基本的来管理状态的函数

const [state,setState] = useState(initialState);

useState()

接收一个参数,也就是state这个参数的初始值

返回一个数组类型,数组的第一个元素就是状态,第二个元素是一个方法,这个方法就是对state进行修改的。

返回值的名字可以随便去取。

function AddPostForm(props){

  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  const handleChange = (e) => {
    if (e.target.name === 'title') {
      setTitle(e.target.value)

    } else if (e.target.name === 'content') {
      setContent(e.target.value)
    }
  };

  const handleSave = async () => {
    if (title.length > 0 && content.length > 0) {
      console.log(title, content)
    }
  };

  return (
      <section>
        <h2>Add a New Post</h2>
        <form>
          <label htmlFor="title">Post Title:</label>
          <input
            type="text"
            id="title"
            name="title"
            value={title}
            onChange={handleChange}
          />
          <label htmlFor="content">Content:</label>
          <textarea
            id="content"
            name="content"
            value={content}
            onChange={handleChange}
          />
          <button type="button" className="button" onClick={handleSave}>
            Save Post
          </button>
        </form>
      </section>
    );
};

这里面就用到了useState()

const [title, setTitle] = useState('');
const [content, setContent] = useState('');

值得注意的是,我们也可以将单个状态写成对象的形式,但是这样在使用setState()的时候就会将状态整个替换,不能像类组件中那样局部修改。

const [{title,content},setState] = useState({
    title:'',
    content:''
})

Effect Hook

我们前面提到类组件的时候,组件的副作用代码会提到组件的生命周期函数中调用。Effect Hook也是可以去解决这个问题的。

useEffect(()=>{effectFn();return clearFn},[...dependencies])

useEffect()接收两个参数,第一个参数是一个函数,第二个参数是一个数组类型的参数

其中effectFn就是我们要执行的带有副作用逻辑的函数,这个返回值,这里写的是clearFn我们可以在clearFn里面做一些清除副作用的逻辑。每一次useEffect()执行的时候,都会先执行这个返回函数,clearFn,在卸载组件的时候也会执行这个函数。

第二个参数是一个可选参数,如果没有指定的话,每次执行useEffect()的时候,effectFn都会被执行,但是如果第二参数指定了,假如说指定了依赖[a,b,c],那只有a,b,c这三个变量发生变化的时候,effectFn才会被执行,如果传入一个空数组,effectFn只会被执行一次。

useEffect()每次在组件被渲染的时候都会被执行,但是不代表effectFn会被执行。

示例

function App() {
  const [articles, setArticles] = useState([])

  useEffect(()=> {
    const fetchArticles = async () => {
      try{
        const response = await client.get('/fakeApi/posts');
        console.log(response)
        setArticles(response.posts);
      }catch(err){
        console.error(err)
      }
    }

    fetchArticles();
  }, [])

  return (
    <div>
      <ArticleList articles={articles}/>
    </div>
  )

}

其他

除了上面两个最常用的,React还提供了其他一些hook的api

具体可以参照Hook API 索引 – React (docschina.org)

image-20210823161042766

自定义hooks

自定义hooks必须要以"use"这个单词作为函数名的开头,封装通用逻辑的函数

import { useEffect } from 'react';
const useMounted = (fn:()=>void) => {
    useEffect(()=>{
        fn();
    },[]);
};

这就相当于自己封装了一个只在加载时执行一次的函数;

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

function useCurrentDate(){
  const [date, setCurrentDate] = useState(new Date());

  const tick = () => {
    setCurrentDate(new Date())
  }

  useEffect(() => {
    const timer = setInterval(()=>{tick()}, 1000)
    return () => {
      clearInterval(timer)
    }
  }, [])

  return date;
}

export default function Clock() {

  const date = useCurrentDate();

  return (
    <div>
      <h1>Current Time: {date.toLocaleTimeString()}.</h1>
    </div>
  );
}

这个例子中创建了一个自定义的hook,将date的改变独立封装成做一个可复用的hook。

规则

使用hook的规则:

  1. 我们只能在函数组件、自定义hook中调用hook
  2. 只能在函数的最顶层控制流中调用hook

这两个示例都不算是最顶层控制流

Why Hook?

  1. 使用Hook可以让我们对组件状态管理逻辑进行复用
  2. 面向React的未来

在hook之前,我们在开发React应用的时候,大量的使用类组件,但是类组件有一些自身的限制,比如说生命周期方法,this的问题导致类组件的复杂度远远高于函数组件,这也给React做一些性能优化,做一些更高级的特性带来了很大的阻碍,所以React推出了Hook这样的功能,让React中的组件变得更加轻量。