React基础知识

React 安装

create-react-app

https://www.html.cn/create-react-app/docs/getting-started/

使用 create-react-app 快速构建 React 开发环境

create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。
create-react-app 自动创建的项目是基于 Webpack + ES6 。
执行以下命令创建项目:
会在当前目录下 创建一个 react 项目

$ cnpm install -g create-react-app
$ create-react-app my-app
$ cd my-app/
$ npm start

npx create-react-app my-app
cd my-app
npm start

在浏览器中打开 http://localhost:3000/ ,结果如下图所示:

项目的目录结构如下:

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js
test-react
├─ README.md // readme说明文档
├─ package.json // 对整个应用程序的描述:包括应用名称、版本号、一些依赖包、以及项目的启动、打包等等(node管理项目必备文件)
├─ public
│    ├─ favicon.ico // 应用程序顶部的icon图标
│    ├─ index.html // 应用的index.html入口文件
│    ├─ logo192.png // 被在manifest.json中使用
│    ├─ logo512.png // 被在manifest.json中使用
│    ├─ manifest.json // 和Web app配置相关
│    └─ robots.txt // 指定搜索引擎可以或者无法爬取哪些文件
├─ src
│    ├─ App.css // App组件相关的样式
│    ├─ App.js // App组件的代码文件
│    ├─ App.test.js // App组件的测试代码文件
│    ├─ index.css // 全局的样式文件
│    ├─ index.js // 整个应用程序的入口文件
│    ├─ logo.svg // 刚才启动项目,所看到的React图标
│    ├─ serviceWorker.js // 默认帮助我们写好的注册PWA相关的代码
│    └─ setupTests.js // 测试初始化文件
└─ yarn.lock

笔记

https://www.html.cn/create-react-app/docs/supported-browsers-features/

通常,在应用程序中,你有许多 UI 组件,并且每个组件都有许多不同的 states(状态)。 例如,一个简单的按钮组件可以具有以下 states(状态):

在常规状态下,带有文本标签。
在禁用模式下。
处于加载状态。

在这里插入图片描述

文件结构

  • src/index.js:

  • import React from ‘react’;    ------react库核心文件,return中的代码翻译的时候,也需要react,所以必须导入

  • import ReactDOM from ‘react-dom’; -------操作dom

  • import App from ‘./App’; ------------为了ReactDOM.rennder渲染单文件组件,

  • import * as serviceWorker from ‘./serviceWorker’; serviceWorker.register() ------为了缓存应用,下次加载应用更快(unregister()不应用)

  • ReactDOM.render(<App />, document.getElementById(‘root’)); ------------获取public/index.html的dom节点,然后渲染App组件

  • src/App.js ---------根组件只有一个,想扩展的时候在App.js里面写一个,然后class child from…就跟上面一样了,这样就可以一直写下去了。

  • react导出方式对应的导入方式       ----------es6导入导出方式

    • import React from “react”;
    • import {Component} from “react”;
      //react 库导出 export default react
      //react 库导出 var Compoennt= React.Compoent ;export {Component}

组件定义

组件允许您将UI拆分为独立可重用的部分,并单独地考虑每个部分。
从概念上讲,组件就像JavaScript函数。 它们接受任意输入(称为“props”),并返回应该出现在屏幕上的React元素。

定义一个组件

功能性组件(functional)

定义个组件的最简单的方式就是写一个Javascript函数。该函数接受一个对象作为参数(我们称之为props,该参数为只读参数),并且返回一个React Element

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

类组件(class component)

class Welcome extends React.Component {
    render() {
        return <h1>hello, {this.props.name}</h1>;
    }
}

上述两个组件从React的角度来看是等效的,类组件有一些额外的功能

组件类中的方法

render() 主要渲染返回,用于返回一个React Component
constructor(props) 构造函数方法,该方法中必须显式调用super(props)
componentDidMount() 组件挂载时的钩子函数
componentWillUnmount() 组件卸载时的钩子函数

渲染一个组件

const element = <Welcome name="Never" />;

当React看到表示用户定义组件的元素时,它将该JSX标签的所有属性放到一个对象中传递给组件。 我们称这个对象为“props”。

props = {
	name: "Never"
}
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Never" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

让我们回顾一下在这个例子中发生了什么:

  • 首先调用ReactDOM.render()方法,并处理组件。
  • React使用{name:‘Never’}作为props调用Welcome组件。
  • 我们的Welcome组件返回一个<h1> Hello,Never </ h1>元素作为结果。
  • React DOM有效地根据<h1> Hello,Never </ h1>来更新DOM。

警告

  • 组件名称始终使用·首字母大写·。
  • 例如,<div />表示一个DOM标签,但<Welcome />表示一个组件,并要求Welcome必须和ReactDOM在一个作用域内。

编写组件-嵌套

组件可以在其返回值中引用去其他组件。 这允许我们对任何级别的细节使用相同的组件抽象。 一个按钮,一个表单,一个对话框,一个屏幕:在React应用程序中,所有这些通常表示为组件。
例如,我们可以创建一个App组件,让它渲染多个Welcome组件:

function Welcome(props) {
   return <h1>Hello, {props.name}</h1>;
}

function App() {
   return (
       <div>
           <Welcome name="zhangyatao" />
           <Welcome name="jiangyanyun" />
           <Welcome name="pangyun" />
       </div>
   );
}

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

抽离组件

永远不要害怕将组件拆分成更小的组件。
例如,考虑这个Comment组件:

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

function formatDate(date) {
    return date.toISOString();
}

function Comment(props) {
    return (
        <div className="Comment">
        
            <div className="UserInfo">
                <img className="avatar"
                     src={props.author.avatarUrl}
                     alt={props.author.name}
                />
                <div className="UserInfo-name">
                    {props.author.name}
                </div>
            </div>
            
            <div className="Comment-text">
                {props.text}
            </div>
            <div className="Comment-date">
                {formatDate(props.date)}
            </div>
            
        </div>
    );
}

ReactDOM.render(
    <Comment author={{
        avatarUrl: 'https://ss0.bdstatic.com/7Ls0a8Sm1A5BphGlnYG/sys/portrait/item/3ae1dc06.jpg',
        name: 'Never'
    }} text={'我的名字是Never'} date={new Date()}/>,
    document.getElementById('root')
);

它接受author(作者),text(内容)和date(日期)作为props,用来描述社交媒体网站上的评论。
这个组件可能很难改变,因为所有的嵌套,它也很难重用其中的单个部分。 让我们从中提取几个组件。

首先,我们将提取avatar:

function Avatar(props) {
    return (
        <img className="Avatar"
            src={props.user.avatarUrl}
            alt={props.user.name}
        />
    );
}

avatar不需要知道它正在Comment中呈现。 这就是为什么我们给它的prop一个更通用的名称:user而不是author。
我们建议从组件自己的角度来命名props,而不是使用它的上下文。

我们现在可以对Comment组件做一点点简化:

function Comment(props) {
    return (
        <div className="Comment">
            <div className="UserInfo">
                <Avatar user={props.author} />
                <div className="UserInfo-name">
                    {props.author.name}
                </div>
            </div>
            <div className="Comment-text">
                {props.text}
            </div>
            <div className="Comment-date">
                {formatDate(props.date)}
            </div>
        </div>
    );
}

接下来,我们将提取一个UserInfo组件,该组件在用户名称旁边呈现一个avatar:

function UserInfo(props) {
    return (
        <div className="UserInfo">
            <avatar uer={props.user} />
            <div className="UserInfo-name">
                {props.user.name}
            </div>
        </div>
    );
}
function Comment(props) {
    return (
       <div className="Comment">
           <UserInfo user={props.author} />
           <div className="Comment-text">
               {props.text}
           </div>
           <div className="Comment-date">
               {formatDate(props.date)}
           </div>
       </div>
    );
}

让一个个可重用组件在大型应用程序中交付使用的过程中,抽离组件起初可能看起来像又脏又累的活儿。 所以有一个好的经验法则:如果UI的一部分被使用了好几次(按钮,面板,头像),或者内部比较复杂的东西(App,FeedStory,评论),一个可重用的组件对它来说可以达到最大的发挥空间。

Props是只读的

无论是将组件声明为功能组件还是类组件,它都不能修改自己的props。 考虑这个计算参数总和的函数:

function sum(a, b) {
    return a + b;
}

这样的函数被称为“纯函数”,因为它们不会改变它们的参数值,并且对于相同的输入总是返回相同的结果。
相反,这个函数是不纯的,因为它改变自己的参数:

function withdraw(account, amount) {
    account.total -= amount;
}

React非常灵活,但它有一个严格的规则:
所有React组件必须像它们的porps的纯函数那样运行。

当然,应用中的UI大部分是动态的,随时间变化。 在下一节中,我们将介绍一个“state”的新概念。 状态允许React组件响应用户操作,网络响应和其他任何内容,随时间更改其输出,而不违反此规则。

实例

    class Welcome extends React.Component {
      //构造函数,在构造函数第一行要调用super
      constructor(props){
        super(props);
        this.state = {date:new Date()}
      }
      //组件绑定的时候设置间歇调用,每隔一秒钟修改date值
      componentDidMount(){
        this.timerID = setInterval(
          () => this.tick(),
          1000
        );
      }
      //组件卸载时取消简写调用
      componentWillUnMount(){
        clearInterval(this.timerID);
      }
      //修改date值
      tick(){
        this.setState({
          date: new Date()
        });
      }
      //渲染,当state中的值发生变化的时候DOM中对应的值也会发生变化
      render(){
        return <h1>hello {this.state.date.toLocaleTimeString()}</h1>
      }
    }
    
ReactDOM.render(<Welcome/>,document.getElementById('app'));

例子

//index.js
import React from 'react';
import RactDOM from 'react-dom';


function tick() {
    const element = (
        <div>
            <h1>hello world</h1>
            <h2>it is {new Date().toLocaleTimeString()}</h2>
        </div>
    );
    ReactDOM.render(
        element,
        document.getElementById('root')
    )
}

在这里插入图片描述

state管理和生命周期钩子

state和生命周期

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

function Clock(props) {
    return (
        <div>
            <h1>hello world</h1>
            <h2>It is {props.date.toLocaleTimeString()}</h2>
        </div>
    );
}

function tick() {
   ReactDOM.render(
       <Clock date={new Date()} />,
       document.getElementById('root')
   );
}

setInterval(tick, 1000);

它缺少了一个关键要求:时钟设置一个定时器和每秒更新UI的事实应该是时钟的实现细节

希望以下能实现更新:

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

因此我们需要添加state到时钟组件

state类似于props,但它是私有的,完全由组件控制。

我们之前提到,定义为类组件具有一些附加功能。 内部state就是:一个只有类组件可用的功能

您可以通过五个步骤将功能组件(如Clock)转换为类组件 :

创建一个与扩展React.Component相同名称的ES6类。
为它添加一个单一的空方法render()。
将函数的主体移动到render()方法中。
在render()主体中用this.props替换props。
删除剩余的空函数声明。

class Clock extends React.Component {
   render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
           </div>
       )
   };
}

Clock现在已经重新定义为类组件而不是之前的功能组件了。
这使我们可以使用额外的功能,如内部state和生命周期钩子

向类组件中添加state

我们将分为三个步骤把date从props移动到state:

1)在render()方法中将this.props.date替换为this.state.date:

class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
}

2)添加一个赋值初始this.state的类构造函数:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>It is {this.state.date.toLocalTimeString()}.</h2>
            </div>
        );
    }
}

注意我们如何将props传递给基类的构造函数:

constructor(props) {
    super(props);
    this.state = {date: new Date()};
}

类组件应该总是用props调用基类构造函数。

3)从元素中删除date prop:

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

我们稍后将定时器代码添加回组件本身。
结果如下所示:

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

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}

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

接下来,我们将使时钟设置自己的定时器,并每秒更新一次。

向类中添加声明周期方法

在具有许多组件的应用程序中,释放组件在销毁时占用的资源非常重要。
我们想要在第一次将时钟渲染到DOM时设置一个计时器。 这在React中称为“安装(mounting)”。
我们还想清除定时器,当时钟产生的DOM被删除。 这在React中称为“卸载(unmounting)"。
我们可以在组件类上声明特殊方法,以便在组件装入和卸载时运行一些代码:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    componentDidMount() {
        // 组件已经安装完毕
    }
    componentWillUnmount() {
        // 组件将要被卸载
    }
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}

这些方法称为“生命周期钩子”。

componentDidMount()钩子在组件输出呈现到DOM之后运行。 这是设置计时器的好地方:

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

注意我们如何保存计时器ID就在这。
虽然this.props是由React本身设置的,并且this.state有一个特殊的含义,如果你需要存储不用于视觉输出的东西,你可以手动地添加额外的字段到类中。
如果你不使用render()中的东西,它不应该放置在state中。
我们将拆除componentWillUnmount()生命周期钩子中的计时器:

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

最后,我们将实现每秒运行的tick()方法。
它将使用this.setState()来调度组件本地state的更新:

class Clock extends React.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>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}
ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

让我们快速回顾一下发生了什么以及调用方法的顺序:

  1. 当将传递给ReactDOM.render()时,React调用Clock组件的构造函数。由于Clock需要显示当前时间,它使用包括当前时间的对象初始化this.state。我们稍后将更新此state。
  2. React然后调用Clock组件的render()方法。这是React如何学习应该在屏幕上显示什么。 React然后更新DOM以匹配时钟的渲染输出。
  3. 当时钟输出插入到DOM中时,React调用componentDidMount()生命周期钩子。在其中,时钟组件要求浏览器设置一个定时器,每秒调用tick()一次。
  4. 每秒钟浏览器调用tick()方法。在其中,Clock组件通过调用setState()和包含当前时间的对象来调度UI更新。由于setState()调用,React知道state已更改,并再次调用render()方法来了解屏幕上应该显示的内容。这个时候,render()方法中的this.state.date将会不同,因此渲染输出将包括更新的时间。 React相应地更新DOM。
  5. 如果时钟组件从DOM中被移除,React将调用componentWillUnmount()生命周期钩子,因此定时器停止。

正确使用state

关于setState()你应该了解三件事情:

不要直接修改state

例如,这将不会重新渲染组件:

// 这是错误的
this.state.comment = 'hello';

应该使用setState()代替:

// 这是正确的

this.setState({comment: 'hello'});

唯一可以分配this.state的地方是构造函数。

state更新可能是异步的

React可以将多个setState()用批处理为单个更新以实现较高的性能。

因为this.props和this.state可能是异步更新的,你不应该依赖它们的值来计算下一个state。

例如,此代码可能无法更新计数器:

// 这是错误的
this.setState({
    counter: this.state.counter + this.props.increment,
});

要解决它,应该使用回调函数而不是对象来调用setState()。 回调函数将接收先前的state作为第一个参数,并将应用更新时的props作为第二个参数:

// 这是正确的
this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
}));

我们使用上面的箭头函数,但它也可以与常规函数一起使用:

// 这同样也是正确的,将剪头函数改为普通函数
this.setState(function(prevState, props) {
   return {
       counter: prevState.counter + prps.increment
   }
});

state更新是经过合并的

当调用setState()时,React会将您提供的对象合并到当前state。

例如,您的state可能包含几个独立变量:

constructor(props) {
    super(props);
    this.state = {
        posts: [],
        comments: []
    }
}

然后,您可以使用单独的setState()来独立地更新它们:

componentDidMount() {
    fetchPosts().then(response => {
        this.setState({
            posts: response.posts
        });
    });
    
    fetchComments().then(response => {
        this.setState({
            comments: response.comments
        }});
    });
}

合并很浅,所以this.setState({comments})不会波及this.state.posts。仅仅只是完全替换了this.state.comments而已。

数据是向下流动的

父组件和子组件都不能知道某个组件是有State的还是无State的,并且它们不应该关心它是否为功能组件或类组件。

这就是为什么State通常被设置为局部变量或封装到组件内部。 除了拥有和设置它的组件之外的其他任何组件都不能访问它。

组件可以选择将其state作为props传递给其子组件:

<h2>Is is {this.state.date.toLocaleTimeString()}.</h2>

这也适用于用户定义的组件:

<formattedDate date={this.state.data} />

FormattedDate组件将在其props中接收date,并且不知道它是来自时钟的state,props还是手动输入:

function FormattedData(props) {
    return <h2>Is is {props.date.toLocaleTimeString()}.</h2>;
}

这通常被称为“自顶向下”或“单向”数据流。 任何state总是由一些特定组件拥有,并且从该state派生的任何数据或UI只能影响树中的“下面”组件。

如果你想象一个组件树作为props的瀑布流,每个组件的state就像一个额外的水源,它可以在任意点连接它,但也向下流。

为了显示所有组件都是真正隔离的,我们可以创建一个App组件来渲染三个<Clock>:

function App() {
    return (
        <div>
            <Clock />
            <Clock />
            <Clock />
        </div>
    );
}

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

事件处理

使用React元素处理事件与处理DOM元素上的事件非常相似。不过有一些语法上的差异:

React事件使用驼峰命名法,而不是全部小写命名。
使用JSX你传递一个函数作为事件处理程序,而不是一个字符串。
例如,HTML:

<button onclick="activeLasers()">
    Active Lasers
</button>

在React中略有不同:

<button onClick={activateLasers}>
    Active Lasers
</button>

另一个区别是,你不能返回false来防止React中的默认行为。 您必须显式调用preventDefault。 例如,使用纯HTML,为了防止打开新页面的默认链接行为,您可以写:

<a href="#" onclick="console.log('The link was clicked'); return false;">
    Click me
</a>

在React中,这可以改为:

function ActiveLink() {
    function handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked');
    }

    return (
        <a href='#' onClick={handleClick}>
            click me
        </a>
    );
}

这里,e是合成过的event。 React根据W3C规范定义这些event,因此你不需要担心浏览器兼容性的问题。

当使用React时,在创建后向DOM元素添加通常不需要调用addEventListener监听器。 相反,只是在最初渲染元素时提供事件监听器。

当您使用ES6类定义组件时,常见的模式是将事件处理程序作为类上的公共方法。 例如,此Toggle组件呈现一个按钮,让用户在“ON”和“OFF”状态之间切换:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};//默认是ON

    // 这个绑定是必要的,要不然该事件中的this就会是当前实例
    this.handleClick = this.handleClick.bind(this);
  }

//在函数中,在严格模式下,this 是未定义的(undefined)
  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

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

你必须十分注意JSX中事件函数的意义。 在JavaScript中,类中的方法默认不绑定this。 如果你忘记绑定this.handleClick中的this关键字并将它传递给了onClick事件,当函数实际被调用时,会报出undefined的错误。

这不是React中特定的行为; 它是JavaScript中函数正常工作的一部分。
一般来说,如果你引用一个方法是后面没有(),如onClick = {this.handleClick},就会绑定该方法。

如果调用bind会使你烦恼,有两种方法可以解决这个问题。 您可以使用属性初始值设定props来正确处理:

class LoggingButton extends React.Component {
  // 使用箭头函数绑定this
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

默认情况下,在Create React App中启用此语法。

如果不使用属性初始化语法,可以在回调中使用箭头函数:

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 给事件传入一个剪头函数也可以绑定this
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}

此语法的问题是,每次LoggingButton渲染时都会创建不同的回调函数。 在大多数情况下,这是要被罚款的。 然而,如果这个回调作为一个prop传递给较低的组件,这些组件可能会做额外的重新渲染。 我们一般建议在构造函数中绑定以避免这种性能问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值