React学习笔记

React学习笔记

入门准备

起步

开发环境:

1、安装 Visual Studio Code

2、安装较新版本的 Node.js

脚手架

Create React App 是一个用于学习 React 的舒适环境,也是用 React 创建新的单页应用的最佳方式。

npx create-react-app my-app		// 创建一个my-app应用

react-app 脚手架的 目录结构

node_modules 存放第三方下载的依赖的包

public     资源目录

src       项目所有的源代码

index.js   整个程序的入口 (react 的理念 all in js)

App.js   项目

.gitignore    用 git 管理代码不想传到 git 上可以使用

​ package-lock.json 依赖的安装包 (一般不用动)

package.json  node 的包文件 和项目介绍 ( 命令行 命令 ) 等

README.md   项目的说明文件

HelloWorld

在src下的index.js中,添加如下代码:

import React from 'react'
import ReactDom from 'react-dom/client'

const root = ReactDom.createRoot(document.getElementById("root"));
root.render(<h1>Hello World</h1>);

在终端下输入:npm start,就可以看见网页中的HelloWorld了。

基础学习

React学习官网:https://react.docschina.org/

JSX 介绍

JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模板语言,但它具有 JavaScript 的全部功能。

// 声明一个常量 
const element = <h1>Hello, world!</h1>;

// 嵌入表达式,在JSX语法中,你可以在大括号内放置任何有效的JavaScript表达式。
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>

// 注释写法
{/*<div id="aaa">sss</div>*/}

// 在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。
function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>
  }
  return <h1>Hello, Stranger.</h1>
}
    
// 指定属性,你可以通过使用引号,来将属性值指定为字符串字面量
const element = <a href="https://www.reactjs.org"> link </a>
    
// 也可以使用大括号,来在属性值中插入一个 JavaScript 表达式
const element = <img src={user.avatarUrl}></img>
    
// 假如一个标签里面没有内容,你可以使用 `/>` 来闭合标签,就像 XML 语法一样:
const element = <img src={user.avatarUrl} />;

// JSX 标签里能够包含很多子元素:
const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);
    
// React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止XSS攻击。
const title = response.potentiallyMaliciousInput;
const element = <h1>{title}</h1>;	// 直接使用是安全的:

// JSX表示对象
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

** 警告:**

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex 。

元素渲染

  • 想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入ReactDOM.createRoot():
const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <h1>Hello, world</h1>;
root.render(element);
  • React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。更新 UI 唯一的方式是创建一个全新的元素,并将其传入 root.render()

考虑一个计时器的例子:

const root = ReactDOM.createRoot(document.getElementById('root'));

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  root.render(element);
}

setInterval(tick, 1000);

组件 & Props

组件分类:函数式组件 & 类组件

下述两个组件在 React 里是等效的。但目前官方推荐使用的是函数式组件

// 函数式组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 类组件
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
渲染组件

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Welcome name="Sara" />;	

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

root.render(element);

注意: **组件名称必须以大写字母开头。**React 会将以小写字母开头的组件视为原生 DOM 标签。

组合组件

组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。

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

function App() {
  return (
    <div>
      <Welcome name="Sara" />      
      <Welcome name="Cahal" />      
    </div>
  );
}
提取组件

将组件拆分为更小的组件。

// 原组件
function App() {
    return(
    	<div className="container">
            <div className="header">
            	<h1>Hello,World</h1>
            </div>
            <div className="content">
                <input type="text" placeholder="Please input message..." />
            </div>
        </div>
    )
}
// 提取组件
function App() {
    return(
    	<div className="container">
            <Header />
            <Content />
        </div>
    )
}
function Header(){
    return{
        <div className="header">
        	<h1>Hello,World</h1>
        </div>
    }
}
function Content(){
    return(
    	<div className="content">
    		<input type="text" placeholder="Please input message..." />
    	</div>
    )
}

Props只读性

  • 组件无论是使用函数声明还是通过 class 声明,都绝不能修改自身的 props。

  • 所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

  • 在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。

Props限制

class Person extends React.Component{
    state = {
        name:"",
        age:"",
        address:""
    }
}
// 对Person的属性进行限制
Person.propTypes={
    name: PropTypes.string.isRequired, // 表示name的value必须为字符串,且必填
}
// 函数的默认属性为func 例speak: PropTypes.func
// 对Person的属性进行默认设置
Person.defaultProps={
    name: "zhangsan"
}

简化写法:

class Person extends React.Component{
    // 对Person的属性进行限制
	static propTypes={
    	name: PropTypes.string.isRequired, // 表示name的value必须为字符串,且必填
	}
    // 函数的默认属性为func 例speak: PropTypes.func
	// 对Person的属性进行默认设置
	static defaultProps={
    	name: "zhangsan"
	}
}

State & 生命周期

关于 State

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。

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

1、不要直接修改 State,而是应该使用 setState():

2、State 的更新可能是异步的

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:

this.setState(
	{data: data},
	() => { console.log(data) }
)

3、State 的更新会被合并

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

this 指向问题
  1. 使用箭头函数,外部环境this指向谁,箭头函数中的this就指向谁
  2. .bind(this)方法,利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
  3. class 的实例方法,利用箭头函数形式的class实例方法
生命周期

img

refs & 事件处理

关于refs

不建议过度使用ref

1、回调ref的写法:

<input ref={(currentNode) => {this.input1 = currentNode}} /> // 此时的this指向的是外部组件
// 省略版写法 <input ref={ c => this.input1 = c } />
const {input1} = this;

但是如果ref回调函数是以内联函数的方式定义的,在组件更新的时候,它会被调用两次,第一次传入的参数为null,第二次才会传入DOM,可以将ref的回调函数定义为class的绑定函数的形式避免上述问题,但在大多数情况下,这是无关紧要的。

2、createRef API写法:

<input ref={this.MyRef} />
MyRef = React.createRef();
console.log(this.MyRef) // {current:input}

React.createRef() 调用后返回一个容器,该容器可以存储被ref标识的节点,但仅限一个,如果有相同的ref标识时,最后一个标识的会覆盖前面一个的。

事件处理

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
<button onClick={activateLasers}>  
    Activate Lasers
</button>

在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式地使用 preventDefault。例如,在 React 中,可能是这样的:

function Form() {
  function handleSubmit(e) {
    e.preventDefault();    
    console.log('You clicked submit.');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

使用 React 时,你一般不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。事实上,你只需要在该元素初始渲染的时候添加监听器即可。

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

    // 为了在回调中使用 `this`,这个绑定是必不可少的    
    this.handleClick = this.handleClick.bind(this);  
  }

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

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。你可以使用 public class fields 语法 :

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.  
   handleClick = () => {    
       console.log('this is:', this);  
   };  
   render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:

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

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。    
      return (      
         <button onClick={() => this.handleClick()}>        
          Click me
      	</button>
    );
  }
}
向事件处理程序传递参数

在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

条件渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。

观察这两个组件:

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

再创建一个 Greeting 组件,它会根据用户是否登录来决定显示上面的哪一个组件。

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {    
      return <UserGreeting />;  
  }  
    return <GuestGreeting />;
}

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<Greeting isLoggedIn={false} />);

与运算符 &&

通过花括号包裹代码,你可以在 JSX 中嵌入表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染:

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 && <h2>You have {unreadMessages.length} unread messages.</h2> }    
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<Mailbox unreadMessages={messages} />);

三目运算符

另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。

阻止组件渲染

在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。

列表 & Key

渲染多个组件。
// 使用 map() 方法来遍历 numbers 数组。将数组中的每个元素变成 <li> 标签,最后我们将得到的数组赋值给 `listItems`:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map( number => <li>{number}</li>);

// 然后,我们可以将整个 listItems 插入到 <ul> 元素中:
<ul>{listItems}</ul>

▲ 但实际上,在渲染的时候将会看到一个警告 a key should be provided for list items

▲ 意思是当你创建一个元素时,必须包括一个特殊的 key 属性。

关于key

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>    
    {number}
  </li>
);

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key

当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key:

const todoItems = todos.map((todo, index) =>
  <li key={index}>    
    {todo.text}
  </li>
);

元素的 key 只有放在就近的数组上下文中才有意义。

比方说,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 <ListItem /> 元素上,而不是放在 ListItem 组件中的 <li> 元素上。

例子:

function ListItem(props) {
  // 这里不需要指定 key:  
  return <li>{props.value}</li>;}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // !key 应该在数组的上下文中被指定    
    <ListItem key={number.toString()} value={number} />  
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
效率问题
  1. 虚拟DOM中key的作用:
    • 简单的说: key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
    • **详细的说:**当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DoM】。随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
      • 旧虚拟DOM中找到了与新虚拟DOM相同的key:
        • (1).若虚拟DOM中内容没变,直接使用之前的真实DOM
        • (2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
  2. 用index作为key可能会引发的问题:
    • 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
    • 如果结构中还包含输入类的DOM:会产生错误DOM更新==>界面有问题。
    • 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
  3. 开发中如何选择key?
    • 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
    • 如果确定只是简单的展示数据,用index也是可以的。

表单

在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。

受控组件

在 HTML 中,表单元素(如<input><textarea><select>)通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:

input标签

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    
      this.setState({value: event.target.value});  
  }
  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>        
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />        
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

textarea 标签

在 React 中,<textarea> 使用 value 属性代替。这样,可以使得使用 <textarea> 的表单和使用单行 input 的表单非常类似:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {      
        value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'    
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    
      this.setState({value: event.target.value});  
  }
  handleSubmit(event) {
    alert('提交的文章: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          文章:
          <textarea value={this.state.value} onChange={this.handleChange} />        
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

select 标签

React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    
      this.setState({value: event.target.value});  
  }
  handleSubmit(event) {
    alert('你喜欢的风味是: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          选择你喜欢的风味:
          <select value={this.state.value} onChange={this.handleChange}>            
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
            <option value="mango">芒果</option>
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

处理多个输入

当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    this.setState({
      [name]: value    });
  }

  render() {
    return (
      <form>
        <label>
          参与:
          <input
            name="isGoing"            
        	type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          来宾人数:
          <input
            name="numberOfGuests"            
			type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

这里使用了 ES6 计算属性名称的语法更新给定输入名称对应的 state 值:

例如:

this.setState({
  [name]: value
});

等同 ES5:

var partialState = {};
partialState[name] = value;
this.setState(partialState);

状态提升

通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。让我们看看它是如何运作的。

官网的例子可能不太好懂,我写了个简单版的例子:

首先,创建输入框组件和App组件:

class Input extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: ""
        }
        this.change = this.change.bind(this)
    }
    change(e) {
        this.setState({
            value: e.target.value,
        })
    }
    render() {
        const name = this.props.name;
        const value = this.state.value;
        const change = this.change;
        return (
            <fieldset>
                <legend>This is Input {name}</legend>
                <input value={value} onChange={change} />
            </fieldset>
        )
    }
}
class App extends React.Component {
    render() {
        return (
            <Fragment>
                <Input name="A" />
                <Input name="B" />
            </Fragment>
        )
    }
}

此时,输入框是各自管各自的,现在的需求是这样,当在第一个输入框输入一个数时,第二个输入框,显示这个数的三倍;同样,在第二个输入框输入一个数时,第一个输入框显示这个数的两倍。

此时就需要实现数据共享,让两个Input组件访问到同一个数据,再进行转换。所以我们把这个数据,提升到父组件的state中去。

class Input extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: ""
        }
        this.change = this.change.bind(this)
    }
    change(e) {
        this.props.change(e.target.value);
    }
    render() {
        const name = this.props.name;
        const value = this.props.value;
        const change = this.change;
        return (
            <fieldset>
                <legend>This is Input {name}</legend>
                <input value={value} onChange={change} />
            </fieldset>
        )
    }
}
class App extends React.Component {
    constructor(props){
        super(props);
        this.change = this.change.bind(this)
    }
    change(e){
        this.setState({
            value: e,
        })
    }
    render() {
        const value = this.state.value;
        const change = this.change;
        return (
            <Fragment>
                <Input name="A" value={value} change={change} />
                <Input name="B" value={value} change={change} />
            </Fragment>
        )
    }
}

此时,已经实现了数据同步,当第一个输入框输入数据时,第二个输入框的数据会进行同步,接下来,进行数据的转换,编写转换函数:

function methodA(num){
    return parseInt(num) * 2;
}
function methodB(num){
    return parseInt(num) * 3;
}

转换函数编写好之后,进行代码修改,使数据能够进行根据名称转换:

class Input extends React.Component {
    constructor(props) {
        super(props);
        this.change = this.change.bind(this)
    }
    change(e) {
        this.props.change(e.target.value);
    }
    render() {
        const name = this.props.name;
        const value = this.props.value;
        const change = this.change;
        return (
            <fieldset>
                <legend>This is Input {name}</legend>
                <input value={value} onChange={change} />
            </fieldset>
        )
    }
}
class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: 'A',
            value: 0
        }
        this.changeA = this.changeA.bind(this);
        this.changeB = this.changeB.bind(this);
    }
    changeA(e) {
        this.setState({
            name: 'A',
            value: e,
        })
    }

    changeB(e){
        this.setState({
            name: 'B',
            value: e,
        })
    }
    render() {
        const value = this.state.value;
        const name = this.state.name;
        const valueA = name === 'A' ? value : methodA(value);
        const valueB = name === 'B' ? value : methodB(value);
        return (
            <Fragment>
                <Input name='A' value={valueA} change={this.changeA} />
                <Input name='B' value={valueB} change={this.changeB} />
            </Fragment>
        )
    }
}

可能这个例子举的不是特别好,但勉强还是能理解状态提升是怎么一个情况了。

安装依赖

如果npm install 太慢,切换国内源:npm config set registry https://registry.npm.taobao.org

注意:如果组件引入在样式之前,那该样式会覆盖组件中的样式

bootstrap

  • 安装v3版本的bootstrap
    • npm install bootstrap@3 --save
  • 引入依赖和样式表
    • import ‘bootstrap/dist/css/bootstrap.min.css’;
    • import ‘bootstrap/dist/js/bootstrap’;
  • 引入Jquery的在线依赖(public下的index.html)
    • < script type="text/javascript"src=“https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js”>

jquery:

  • npm i jquery -S
  • import $ from ‘jquery’

数据交互

建议学习 Promise 学习地址:https://www.apiref.com/javascript-zh/Reference/Global_Objects/Promise.htm

axios (常用)

学习地址:axios中文文档|axios中文网 | axios (axios-js.com)

fetch (了解)

window内置,不借助于xhr,axios和JQuery都是对xhr的封装,学习地址:Fetch API官方文档_w3cschool

练习参考:

    handleClick = () => {
        fetch(`http://localhost:8081/main/student/students`).then(
            response => {
                console.log('连接服务器成功了');
                return response.json();
            },
            error => {
                console.log('连接服务器失败了');
                return new Promise(()=>{})
            }
        )
        .then(
            response => {
                console.log(response)
            },
            error => {
                console.log(error)
            }
        )
    }

优化写法:

    handleClick = () => {
        fetch(`http://localhost:8081/main/student/students`).then(
            response => {
                console.log('连接服务器成功了');
                return response.json();
            },
        )
        .then(
            response => {
                console.log(response)
            },
        )
        .catch(
        	error => {console.log("请求失败",error)}
        )
    }

再优化:

try {
	const response = await fetch(`http://localhost:8081/main/student/students`)
    const data = await response.json()
	console.log(data);
}catch (error) {
	console.log('请求出错',error)
}

跨域问题

前端可以进行配置代理

1、在package.json文件中添加

"proxy":"http://localhost:8081"

2、在src下创建setupProxy.js文件

const proxy = require('http-proxy-middleware')
module.exports = function (app) {
    app.use(
        proxy('/main', {    // 哪些前缀的请求进行转发,请求带有/main就进行转发
            target: 'http://localhost:8081', // 转发路径
            changeOrigin: true,
            //pathRewirte:{'^/main':""} //如果加了前缀,导致路径不对,这里可以将前缀替换为空字串,转为正确路径
        }),
        proxy('e', {
            target: 'http://localhost:3000',
            changeOrigin: true,
        })
    )
}

这里注意一个细节问题,高版本的http-proxy-middleware写法如下:

const proxy = require('http-proxy-middleware')
module.exports = function (app) {
    app.use(
        proxy.createProxyMiddleware('/main', {    // 哪些前缀的请求进行转发,请求带有/main就进行转发
            target: 'http://localhost:8081', // 转发路径
            changeOrigin: true, // 控制服务器收到的请求头中Host字段的值
            //pathRewirte:{'^/main':""} //重写请求路径,这里可以将前缀替换为空字串,转为正确路径
        }),
        proxy.createProxyMiddleware('e', {
            target: 'http://localhost:3000',
            changeOrigin: true,
        })
    )
}

后端解决跨域

创建一个config文件夹,编写CorsConfig类

package com.hy.maintest.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")//允许哪些域访问
                .allowedMethods("GET","POST","PUT","DELETE","HEAD","OPTIONS")//允许哪些方法访问
                .allowCredentials(true)//是否允许携带cookie
                .maxAge(3600)//设置浏览器询问的有效期
                .allowedHeaders("*");//
    }
}

消息订阅-发布机制

  1. 工具库:PubSubJS

  2. 下载:npm install pubsub-js --save

  3. 使用:

    • ​ import PubSub from ‘pubsub-js’

    • ​ PubSub.subscribe(‘delete’,function(data){}) //订阅

    • ​ PubSub.publish(‘delete’,data) //发布消息

用于不同组件之间的数据通信,以下例子,通过Header和Content进行通信测试:

App.js

import React from "react";
import Header from "./Conponets/Header";
import Content from "./Conponets/Content";
export default class App extends React.Component {
    render() {
        return (
            <div>
                <Header />
                <Content />
            </div>
        )
    }
}

Header.js

import React from "react";
import PubSub from "pubsub-js"; // 引入PubSubJS
export default class Header extends React.Component {
   	state = {
        value: ""
   	}
    componentDidMount() {
        this.token = PubSub.subscribe('title', (_, data) => {	//组件挂载时,订阅消息,消息名称为title,内容是data
            this.setState({
                value: data,
            })
        })
    }
    componentwi1lUnmount(){
		PubSub.unsubscribe(this.token)) // 组件卸载时,注销消息订阅
    }
    render() {
        return (
            <div>
                This is a {this.state.value}
            </div>
        )
    }
}

Content.js

import React from "react";
import PubSub from "pubsub-js";
export default class Content extends React.Component {
    state = {
        value: ""
    }
    handleChange = (e) => {
        const { value } = e.target
        this.setState({
            value: value,
        })
    }
    handleClick = () => {
        const { value } = this.state;
        PubSub.publish('title', value)	// 发布消息,消息名称为title,内容为data
    }
    render() {
        const { value } = this.state;
        return (
            <div>
                <input onChange={this.handleChange} value={value} placeholder="请输入信息" /><br />
                <button onClick={this.handleClick}>点击展示</button>
            </div>
        )
    }
}

ReactRouter (v5)

路由介绍

SPA的理解:

  1. 单页面应用
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,只会进行局部更新
  4. 数据通过ajax请求获取,并在前端异步展示

路由的理解:

前端路由:

  • 浏览器端路由,value是component,用于展示页面内容
  • 注册路由:< Route path=“/test” component={Test} >
  • 工作过程:当浏览器的path变为/test时,当前组件就会变为Test组件

react-router-dom的理解:

  • react的一个插件库
  • 专门用来实现一个SPA应用
  • 基于react的项目都会用到此库

组件分类:

  • 一般组件:直接引入,编写标签渲染(一般组件一般放在components包下)组件传什么props,就收到什么
  • 路由组件:直接引入,通过路由渲染(路由组件一般放在pages包下)会收到三个固定属性:history,match,location

通常情况下,path和element是一一对应的,多个路由建议用Switch包含,可以提高路由匹配效率

路由的模糊匹配

输入的路径要包含匹配的路径

  • /a/b/c 和 /a匹配成功
  • /b/a/c 和 /a匹配失败
  • < Route exact path=“” /> 开启严格匹配,一般没有影响页面的展现,不建议开启、、

Redirect用法

当所有的路由都没有被匹配时,默认展示Redirect的匹配页面。

嵌套路由

除一级路由,后续的路由path都应带有上一级路由的path

  • 一级路由:/a
  • 二级路由:/a/b
  • 三级路由:/a/b/c

路由传参

params参数

  • 路由链接(携带参数): <Link to=‘/demo/test/tom/18’}>详情< /Link>
  • 注册路由(声明接收): < Route path=“/demo/test/: name/ :age” component={Test} />
  • 接收参数: const {id,title} = this.props.match.params

search参数

  • import qs from ‘querystring’

  • 路由链接(携带参数):<Link to={‘/demo/test?name=tom&age=18’ }>详情< /Link>

  • 注册路由(无需声明,正常注册即可): < Route path=“/demo/test” component=(Test}/>

  • 接收参数: const {search} = this.props.location.serch

  • 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

state参数

  • 路由链接(携带参数):<Link to=({path : ’ /demo/test’ ,state:{name : ’ tom’ ,age:18}}}>详情< /Link>
  • 注册路由(无需声明,正常注册即可): < Route path=“/demo/test” component={Test}/>
  • 接收参数: this.props. location.state
  • 备注:刷新也可以保留住参数

编程式路由导航

操作history进行路由操作

  • this.prosp.history.push()
  • this.prosp.history.replace()
  • this.prosp.history.goBack()
  • this.prosp.history.goForward()
  • this.prosp.history.go()

withRouter的作用

让一个一般组件能够获得路由组件的相关API,语法export default withRouter(Test)

BrowserRouter和HashRouter的区别

1.底层原理不一样:

​ BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。HashRouter使用的是URL的哈希值。

2.ur1表现形式不一样

​ BrowserRouter的路径中没有#,例如: localhost:3000/demo/test

​ HashRouter的路径包含#,例如: localhost: 3000/#/demo/test

3.刷新后对路由state参数的影响

​ (1).BrowserRouter没有任何影响,因为state保存在history对象中。

​ (2).HashRouter刷新后会导致路由state参数的丢失。

4.备注: HashRouter可以用于解决一些路径错误相关的问题。

ReactRouter (v6)

安装:

npm install react-router-dom --save

一级路由

const App = () => {
    return (
        <BrowserRouter>
            <div>
                <NavLink to={"/page01"} >page01</NavLink>
                <NavLink to={"/page02"} >page02</NavLink>
            </div>
            <div>
                <Routes>
                    <Route path="/page01" element={<Page01 />} />
                    <Route path="/page02" element={<Page02 />} />
                </Routes>
            </div>
        </BrowserRouter>
    )
};

重定向

const App = () => {
    return (
        <BrowserRouter>
            <div>
                <NavLink to={"/page01"} >page01</NavLink>
                <NavLink to={"/page02"} >page02</NavLink>
            </div>
            <div>
                <Routes>
                    <Route path="/page01" element={<Page01 />} />
                    <Route path="/page02" element={<Page02 />} />
                    <Route path="/" element={<Navigate to={"/page01"} />} /> // 重定向
                </Routes>
            </div>
        </BrowserRouter>
    )
}

路由表

新建一个文件夹,文件夹下创建routes.js

export default [
    {
        path: "/page01",
        element: <Page01 />,
        children: [	// 二级路由
            {
                path:'page01_01',
                element:<Page0101 />,
            },
            {
                path:'page01_02',
                element:<Page0102 />,
            },
            {
                path:'page01_03',
                element:<Page0103 />,
            },
        ],
    },
    {
        path: "/page02",
        element: <Page02 />
    },
    {
        path: "/",
        element: <Navigate to={'/page01'} />
    },
]

展示页面

<div>
	<div>
		<NavLink to={"/page01/page01_01"} >page01_01</NavLink>
       	<NavLink to={"/page01/page01_02"} >page01_02</NavLink>
        <NavLink to={"/page01/page01_03"} >page01_03</NavLink>
  	</div>
    <div>
		<Outlet /> // 展示
	</div>
</div>

路由传参

第一种:useParams

路由表里面设置占位:

{
	path: 'content/:id/:content',
	element: <Content />
}

Link里面进行params参数传递

<div>
	<ul>
		<li><Link to={`content/${1}/${'content01'}`} >item01</Link></li>
        <li><Link to={`content/${2}/${'content02'}`} >item02</Link></li>
        <li><Link to={`content/${3}/${'content03'}`} >item03</Link></li>
    </ul>
    <hr />
    <Outlet />
</div>

获取传递的params

export default function Content() {
    const { id, content } = useParams();
    return (
        <div>
            <span>{id}</span>
            <span>{content}</span>
        </div>
    )
}

第二种:useSearchParams

路由表不用占位。

Link里面进行参数传递

<li><Link to={`content?id=${1}&content=${'content01'}`} >item01</Link></li>
<li><Link to={`content?id=${2}&content=${'content02'}'}`} >item02</Link></li>
<li><Link to={`content?id=${3}&content=${'content03'}'}`} >item03</Link></li>

获取传递的params

<div>
	<ul>
		<li><Link to={`content?id=${1}&content=${'content01'}`} >item01</Link></li>
        <li><Link to={`content?id=${2}&content=${'content02'}'}`} >item02</Link></li>
        <li><Link to={`content?id=${3}&content=${'content03'}'}`} >item03</Link></li>
   	</ul>
	<hr />
	<Outlet />
</div>

第三种:useLocation

路由表不用占位。

Link里面进行参数传递

<li><Link to='content' state={{ id: 1, content: 'content01' }} >item01</Link></li>
<li><Link to='content' state={{ id: 2, content: 'content02' }} >item02</Link></li>
<li><Link to='content' state={{ id: 3, content: 'content03' }} >item03</Link></li>

获取传递的params

const { state: { id, content } } = useLocation();
    return (
        <div>
            <span>{id}</span>
            <span>{content}</span>
        </div>
    )

编程式路由导航

使用useNavigate:

const navigate = useNavigate();

function handleClick() {
	navigate('content', { 
        repalce: false,
        state: { id: 1, content: 'content01' } 
    });	// 这里用的useLocation传值
}

<button onClick={handleClick}>点击跳转并传值</button>

// 前进
navigate(1)
// 后退
navigate(-1)

Redux

redux学习文档: https://redux.js.org/

react-redux学习文档:https://react-redux.js.org/

原理图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rplP8rdl-1661242598918)(C:\Users\86151\AppData\Roaming\Typora\typora-user-images\image-20220815162726929.png)]

基本使用

  • 明确两个概念:
    • UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
    • 容器组件:负责和redux通信,将结果交给UI组件。
  • 如何创建一个容器组件–—靠react-redux 的connect函数
    • connect(mapstateToProps,mapDispatchToProps)(UI组件)
      • -mapstateToProps:映射状态,返回值是一个对象
      • -mapDispatchToProps:映射操作状态的方法,返回值是一个对象
  • 备注:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
  • 注:connect也可以传入对象,会自动帮action进行dispatch分发

Provider的使用

export default class App extends React.Component {
    render() {
        return (
            // 给所有组件设置store
            <Provider store={store}>
                <Count />
                <Count1 />
                <Count2 />
                <Count3 />
            </Provider>
        )
    }
}

整合UI组件和容器组件

import { connect } from 'react-redux'
import { calculation, calculationAsync } from '../redux/count_action'
import { useState } from "react"

// UI组件
const CountUI = (props) => {

    const [number, setNumber] = useState(0)

    function handleChange(e) {
        setNumber(e.target.value)
    }

    function handleClick(e) {
        const type = e.target.value
        props.calculation(type, number)
    }

    function handleAsyncClick(e) {
        const type = e.target.value
        props.calculationAsync(type, number)
    }

    return (
        <div>
            <h1>{props.result}</h1>
            <select onChange={handleChange}>
                <option>0</option>
                <option>1</option>
                <option>2</option>
                <option>3</option>
                <option>4</option>
            </select>
            <br /><br />
            <button onClick={handleClick} value="+">+</button>&nbsp;
            <button onClick={handleClick} value="-">-</button>&nbsp;
            <button onClick={handleClick} value="*">*</button>&nbsp;
            <button onClick={handleClick} value="/">/</button>&nbsp;
            <button onClick={handleAsyncClick} value="+">异步加(延迟一秒)</button>&nbsp;
        </div>
    )
}

// 容器组件
// 优化版
export default connect(
    state => ({ result: state }),
    {
        // 会帮做dispatch分发
        calculation: calculation,
        calculationAsync: calculationAsync
    }
)(CountUI)

优化总结

  • 容器组件和UI组件混成一个文件

  • 无需自己给容器组件传递store,给< App/>包裹一个< Provider store={store}>即可。

  • 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。

  • (mapDispatchToProps也可以简单的写成一个对象

  • 一个组件要和redux“打交道”要经过那几步?

    • 定义好UI组件—不暴露

    • 引入connect生成一个容器组件,并暴露,写法如下:connect(state => ({key : value}){key : xxxxxAction})(UI组件)

    • 在UI组件中通过this.props.xxxxxxx读取和操作状态

数据共享

  1. 定义一个Pserson组件,和Count组件通过redux共享数据。
  2. 为Person组件编写: reducer、action,配置constant常量。
  3. 重点: Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
  4. 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

combineReducers合并代码:

import { createStore, applyMiddleware, combineReducers } from 'redux'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
import thunk from 'redux-thunk'

const allReducer = combineReducers({
    countReducer: countReducer,
    personReducer: personReducer
})

// 提供数据
export default createStore(allReducer, applyMiddleware(thunk))

redux的开发者工具

浏览器装插件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLM97BO8-1661242598919)(C:\Users\86151\AppData\Roaming\Typora\typora-user-images\image-20220816164315397.png)]

npm add redux-devtools-extension

import { createStore, applyMiddleware, combineReducers } from 'redux'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
import thunk from 'redux-thunk'
// 引入
import { composeWithDevTools } from 'redux-devtools-extension'

const allReducer = combineReducers({
    countReducer: countReducer,
    personReducer: personReducer
})

// 提供数据
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))

Hook

State Hook

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Effect Hook

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

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("useEffect")
  },[]); 
    // []相当于监视器,当为空值时,相当于componentDidMount,
    // 当为[count],相当于监视count的更新,count更新时调用
    // useEffect中的函数的返回值为一个函数时,返回的函数相当于componentUnMount

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

语法和说明:

useEffect(() => {
	//在此可以执行任何带副作用操作
	return () => { //在组件卸载前执行
			//在此做一些收尾工作,比如清除定时器/取消订阅等
    }
},[statevalue])//如果指定的是[,回调函数只会在第一次render())后执行

Ref Hook

const Test = () => {
    
    const input = React.userRef()
    
    function show() {
        alert(input.current.value)
    }
    
    return(
        <div>
        	<input type='text' ref={input} />
			<button onClick={show}>点我展示数据</button>
        </div>
    )
}

其他Hook(了解)

useNavigationType

  • 作用:返回当前的导航类型(用户是如何来到当前页面的)。
  • 返回值:POP 、PUSH、REPLACE 。
  • 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

useOutlet

作用:用来呈现当前组件中要渲染的嵌套路由。

示例代码:

const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象

扩展内容

LazyLoad

一般在路由组件中使用

import React,{component,lazy,Suspense} from 'react'
// 更换要懒加载的组件引入方式
// 原:import Test from './components/test' 改为下面这种
const Test = lazy(() => import('./components/test'))

<Suspense fallback={jsx或者组件}>	// 网速较慢,先加载fallback
	<Route path='' component=''/>	      
</Suspense>

Context

理解

一种组件间的通信方法,适用于祖组件后代组件

1)创建context容器对象:
const XxxContext =React.createContext()

2)渲染子组时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据:
	<XXXContext.Provider value={数据}>
		子组件
	</xxxContext.Provider>
	
3)后代组件读取数据:
	//第一种方式:仅适用于类组件
	static contextType = xxxContext //声明接收context
	this.context //读取context中的value数据
	
	//第二种方式:函数组件与类组件都可以
	<xxxContext.Consumer>
		{
			value => ( // value就是context中的value数据
				要显示的内容
			)
		}
	</xxxContext.consumer>

注意:在应用开发中一般不用context,一般都用它封装的react插件


PureComponent

使用PureComponent

PureComponent重写了shouldcomponentupdate(),只有state或props数据有变化才返回true注意:

但也可以手动重写shouldcomponentupdate(nextProps,nextState)

只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false不要直接修改state数据,而是要产生新数据

项目中一般会如此优化
,比如清除定时器/取消订阅等
}
},[statevalue])//如果指定的是[,回调函数只会在第一次render())后执行


### Ref Hook

```jsx
const Test = () => {
    
    const input = React.userRef()
    
    function show() {
        alert(input.current.value)
    }
    
    return(
        <div>
        	<input type='text' ref={input} />
			<button onClick={show}>点我展示数据</button>
        </div>
    )
}

其他Hook(了解)

useNavigationType

  • 作用:返回当前的导航类型(用户是如何来到当前页面的)。
  • 返回值:POP 、PUSH、REPLACE 。
  • 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

useOutlet

作用:用来呈现当前组件中要渲染的嵌套路由。

示例代码:

const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象

扩展内容

LazyLoad

一般在路由组件中使用

import React,{component,lazy,Suspense} from 'react'
// 更换要懒加载的组件引入方式
// 原:import Test from './components/test' 改为下面这种
const Test = lazy(() => import('./components/test'))

<Suspense fallback={jsx或者组件}>	// 网速较慢,先加载fallback
	<Route path='' component=''/>	      
</Suspense>

Context

理解

一种组件间的通信方法,适用于祖组件后代组件

1)创建context容器对象:
const XxxContext =React.createContext()

2)渲染子组时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据:
	<XXXContext.Provider value={数据}>
		子组件
	</xxxContext.Provider>
	
3)后代组件读取数据:
	//第一种方式:仅适用于类组件
	static contextType = xxxContext //声明接收context
	this.context //读取context中的value数据
	
	//第二种方式:函数组件与类组件都可以
	<xxxContext.Consumer>
		{
			value => ( // value就是context中的value数据
				要显示的内容
			)
		}
	</xxxContext.consumer>

注意:在应用开发中一般不用context,一般都用它封装的react插件


PureComponent

使用PureComponent

PureComponent重写了shouldcomponentupdate(),只有state或props数据有变化才返回true注意:

但也可以手动重写shouldcomponentupdate(nextProps,nextState)

只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false不要直接修改state数据,而是要产生新数据

项目中一般会如此优化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值