Fork me on GitHub

优质博客~收集

# 博客收集

# 长期更新的前端 Weekly

# 优秀技术官网

# 优秀知乎专栏

# 国外其他优秀站点

# 列举长期更新的周刊源,每周精选的主要来源。

印记中文周刊

# 编程狂人

https://www.tuicool.com/mags

hexo 无法搜索问题

# 一般 Hexo 博客无法搜索主要有 2 种以下情况:

  • 搜索插件没有配置好
  • 文章中包含特殊字符

# 一、搜索插件的配置

编辑 站点配置文件 ,新增以下内容到任意位置:

search:
  path: search.xml
  field: post
  format: html
  limit: 10000

编辑 主题配置文件 ,启用本地搜索功能:

# Local search
local_search:
  enable: true

安装搜索插件

 npm install hexo-generator-searchdb --save

+ hexo-generator-searchdb@1.0.8
added 119 packages in 8.327s

# 二、文章中特殊字符

# 检查 search.mxl 的状态

ggSet1

# 浏览器直接访问 search.xml 文件看看:

8ZPJND

Redux 学习指南

# Redux 学习指南

# 设计思想

Redux 的设计思想很简单,就两句话。

(1)Web 应用是一个状态机,视图与状态是一一对应的。

(2)所有的状态,保存在一个对象里面。

请务必记住这两句话,下面就是详细解释。

# Redux 三大核心

  1. 单一数据源
  2. State 是只读的
  3. 使用纯函数来执行修改

uLpZu4

store、components、actionCreaters、reducers 的关系即为:

  • 首先有一个组件,组件要去获取 store 中的一些数据
  • actionCreaters 通过 dispatch (action) 方法 让 store 知道 组件要获取数据
  • store 在 reducer 查组件需要什么数据,reducer 返回组件应该拿到的数据
  • store 获得数据后把数据 返给 组件

# Redux 核心

# state 状态

  • DomainState:服务器返回的 State
  • UI State : 关于当前组件的 State
  • App State:全局的 State

Store 对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

当前时刻的 State,可以通过 store.getState() 拿到。

import { createStore } from 'redux';
const store = createStore(fn);

const state = store.getState();

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

# Action 事件对象

  • 本质就是一个 JS 对象
  • 必须包含 Type 属性
  • 只是描述了有事情要发生,并没有描述如何去更新 State

State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action 是一个对象。其中的 type 属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

上面代码中,Action 的名称是 ADD_TODO ,它携带的信息是字符串 Learn Redux

可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。

# Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

const ADD_TODO = '添加 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');

上面代码中, addTodo 函数就是一个 Action Creator。

# Store

  • 用来把 action 喝 reducer 关联到一起
  • 通过 createStore 来构建 store
  • 通过 subscribe 来注册监听
  • 通过 dispatch 来发送 action

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供 createStore 这个函数,用来生成 Store。

import { createStore } from 'redux';
const store = createStore(fn);

上面代码中, createStore 函数接受另一个函数作为参数,返回新生成的 Store 对象。

# store.dispatch()

store.dispatch() 是 View 发出 Action 的唯一方法。

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});

上面代码中, store.dispatch 接受一个 Action 对象作为参数,将它发送出去。

结合 Action Creator,这段代码可以改写如下。

store.dispatch(addTodo('Learn Redux'));

# Reducer

  • 本质就是一个函数
  • 响应发送过来的 action
  • 函数接收两个参数,第一个是初始化 state,第二个是发送过来的 action
  • 必须要有 return 返回值

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

const reducer = function (state, action) {
  // ...
  return new_state;
};

整个应用的初始状态,可以作为 State 的默认值。下面是一个实际的例子。

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};

const state = reducer(1, {
  type: 'ADD',
  payload: 2
});

上面代码中, reducer 函数收到名为 ADD 的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。

实际应用中,Reducer 函数不用像上面这样手动调用, store.dispatch 方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入 createStore 方法。

import { createStore } from 'redux';
const store = createStore(reducer);

上面代码中, createStore 接受 Reducer 作为参数,生成一个新的 Store。以后每当 store.dispatch 发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

为什么这个函数叫做 Reducer 呢?因为它可以作为数组的 reduce 方法的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组。

const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];

const total = actions.reduce(reducer, 0); // 3

上面代码中,数组 actions 表示依次有三个 Action,分别是加 0 、加 1 和加 2 。数组的 reduce 方法接受 Reducer 函数作为参数,就可以直接得到最终的状态 3

# 纯函数

由于 Reducer 是纯函数,就可以保证同样的 State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

// State 是一个对象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一个数组
function reducer(state, action) {
  return [...state, newItem];
}

最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象。

# store.subscribe()

Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

import { createStore } from 'redux';
const store = createStore(reducer);

store.subscribe(listener);

显然,只要把 View 的更新函数(对于 React 项目,就是组件的 render 方法或 setState 方法)放入 listen ,就会实现 View 的自动渲染。

store.subscribe 方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

# 工作流程

首先,用户发出 Action。

store.dispatch(action);

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action);

State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数
store.subscribe(listener);

listener 可以通过 store.getState() 得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}

# Store 的实现

上一节介绍了 Redux 涉及的基本概念,可以发现 Store 提供了三个方法。

  • store.getState()
  • store.dispatch()
  • store.subscribe()
import { createStore } from 'redux';
let { subscribe, dispatch, getState } = createStore(reducer);

createStore 方法还可以接受第二个参数,表示 State 的最初状态。这通常是服务器给出的。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

上面代码中, window.STATE_FROM_SERVER 就是整个应用的状态初始值。注意,如果提供了这个参数,它会覆盖 Reducer 函数的默认初始值。

下面是 createStore 方法的一个简单实现,可以了解一下 Store 是怎么生成的。

const createStore = (reducer) => {
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    }
  };

  dispatch({});

  return { getState, dispatch, subscribe };
};

# Redux 简单 demo

src/action/index.js:

const ADD_TODO = "ADD_TODO";

export function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  };
}

src/reducer/index.js

const demoInitialState = {
    value: '默认值'
}
const reducer = (state = demoInitialState, action) => {
    console.log(state, action);
    switch (action.type) {
        case 'ADD_TODO':
            return {
                ...state, ...action
            }
            default:
                return state
    }
}

module.exports = {
    reducer
}

src/store/index.js:

import { createStore } from "redux";

// 倒入我们自己创建的 reducer

import { reducer } from "../reducer";

// 构建 store
const store = createStore(reducer);

export default store;

Home.jsx:

import React, { useEffect } from "react";

//导入 store
import store from "../store";

// 导入 action
import { addTodo } from "../action";

const Home = () => {
  const handleClick = () => {
    store.dispatch(addTodo("11111"));
  };

  useEffect(() => {
    store.subscribe(() => {
      console.log(store.getState());
    });
  });

  return (
    <div>
      <button onClick={handleClick}>Home 按钮</button>
    </div>
  );
};

export default Home;

MYLa1c

必须要会的 50 个 React 面试题

# React 面试题参考🔗

以下是面试官最有可能问到的 50 个 React 面试题和答案。为方便你学习,我对它们进行了分类:

  • 基本知识
  • React 组件
  • React Redux
  • React 路由

# 基本知识

# 1. 区分 Real DOM 和 Virtual DOM

Real DOM Virtual DOM
1. 更新缓慢。 1. 更新更快。
2. 可以直接更新 HTML。 2. 无法直接更新 HTML。
3. 如果元素更新,则创建新 DOM。 3. 如果元素更新,则更新 JSX 。
4. DOM 操作代价很高。 4. DOM 操作非常简单。
5. 消耗的内存较多。 5. 很少的内存消耗。

# 2. 什么是 React?

  • React 是 Facebook 在 2011 年开发的前端 JavaScript 库。
  • 它遵循基于组件的方法,有助于构建可重用的 UI 组件。
  • 它用于开发复杂和交互式的 Web 和移动 UI。
  • 尽管它仅在 2015 年开源,但有一个很大的支持社区。

# 3. React 有什么特点?

React 的主要功能如下:

  1. 它使用 ** 虚拟 DOM ** 而不是真正的 DOM。
  2. 它可以用服务器端渲染
  3. 它遵循单向数据流或数据绑定。

# 4. 列出 React 的一些主要优点。

React 的一些主要优点是:

  1. 它提高了应用的性能
  2. 可以方便地在客户端和服务器端使用
  3. 由于 JSX,代码的可读性很好
  4. React 很容易与 Meteor,Angular 等其他框架集成
  5. 使用 React,编写 UI 测试用例变得非常容易

# 5. React 有哪些限制?

React 的限制如下:

  1. React 只是一个库,而不是一个完整的框架
  2. 它的库非常庞大,需要时间来理解
  3. 新手程序员可能很难理解
  4. 编码变得复杂,因为它使用内联模板和 JSX

# 6. 什么是 JSX?

JSX 是 J avaScript XML 的简写。是 React 使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法。这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能。下面是 JSX 的一个例子:

render(){
    return(
        <div>
            <h1> Hello World from Edureka!!</h1>
        </div>
    );
}
复制代码

# 7. 你了解 Virtual DOM 吗?解释一下它的工作原理。

Virtual DOM 是一个轻量级的 JavaScript 对象,它最初只是 real DOM 的副本。它是一个节点树,它将元素、它们的属性和内容作为对象及其属性。 React 的渲染函数从 React 组件中创建一个节点树。然后它响应数据模型中的变化来更新该树,该变化是由用户或系统完成的各种动作引起的。

Virtual DOM 工作过程有三个简单的步骤。

  1. 每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲染。

    Virtual DOM 1

  2. 然后计算之前 DOM 表示与新表示的之间的差异。

    Virtual DOM 2

  3. 完成计算后,将只用实际更改的内容更新 real DOM。

    Virtual DOM 3

# 8. 为什么浏览器无法读取 JSX?

浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。

# 9. 与 ES5 相比,React 的 ES6 语法有何不同?

以下语法是 ES5 与 ES6 中的区别:

  1. require 与 import
// ES5
var React = require('react');

// ES6
import React from 'react';
复制代码
  1. export 与 exports
// ES5
module.exports = Component;

// ES6
export default Component;
复制代码
  1. component 和 function
// ES5
var MyComponent = React.createClass({
    render: function() {
        return
			<h3>Hello Edureka!</h3>;
    }
});

// ES6
class MyComponent extends React.Component {
    render() {
        return
			<h3>Hello Edureka!</h3>;
    }
}
复制代码
  1. props
// ES5
var App = React.createClass({
    propTypes: { name: React.PropTypes.string },
    render: function() {
        return
			<h3>Hello, {this.props.name}!</h3>;
    }
});

// ES6
class App extends React.Component {
    render() {
        return
			<h3>Hello, {this.props.name}!</h3>;
    }
}
复制代码
  1. state
// ES5
var App = React.createClass({
    getInitialState: function() {
        return { name: 'world' };
    },
    render: function() {
        return
	        <h3>Hello, {this.state.name}!</h3>;
    }
});

// ES6
class App extends React.Component {
    constructor() {
        super();
        this.state = { name: 'world' };
    }
    render() {
        return
	        <h3>Hello, {this.state.name}!</h3>;
    }
}
复制代码

# 10. React 与 Angular 有何不同?

主题 React Angular
1. 体系结构 只有 MVC 中的 View 完整的 MVC
2. 渲染 可以在服务器端渲染 客户端渲染
3. DOM 使用 virtual DOM 使用 real DOM
4. 数据绑定 单向数据绑定 双向数据绑定
5. 调试 编译时调试 运行时调试
6. 作者 Facebook Google

# React 组件

# 11. 你理解 “在 React 中,一切都是组件” 这句话。

组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。

# 12. 解释 React 中 render () 的目的。

每个 React 组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 <form><group><div> 等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。

# 13. 如何将两个或多个组件嵌入到一个组件中?

可以通过以下方式将组件嵌入到一个组件中:

class MyComponent extends React.Component{
    render(){
        return(
			<div>
            	<h1>Hello</h1>
                <Header/>
            </div>
        );
    }
}
class Header extends React.Component{
    render(){
        return
			<h1>Header Component</h1>
   };
}
ReactDOM.render(
    <MyComponent/>, document.getElementById('content')
);
复制代码

# 14. 什么是 Props?

Props 是 React 中属性的简写。它们是只读组件,必须保持纯,即不可变。它们总是在整个应用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。

# 15. React 中的状态是什么?它是如何使用的?

状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与 props 不同,它们是可变的,并创建动态和交互式组件。可以通过 this.state() 访问它们。

# 16. 区分状态和 props

条件 State Props
1. 从父组件中接收初始值 Yes Yes
2. 父组件可以改变值 No Yes
3. 在组件中设置默认值 Yes Yes
4. 在组件的内部变化 Yes No
5. 设置子组件的初始值 Yes Yes
6. 在子组件的内部更改 No Yes

# 17. 如何更新组件的状态?

可以用 this.setState() 更新组件的状态。

class MyComponent extends React.Component {
    constructor() {
        super();
        this.state = {
            name: 'Maxx',
            id: '101'
        }
    }
    render()
        {
            setTimeout(()=>{this.setState({name:'Jaeha', id:'222'})},2000)
            return (
				<div>
                	<h1>Hello {this.state.name}</h1>
					<h2>Your Id is {this.state.id}</h2>
                </div>
            );
        }
    }
ReactDOM.render(
    <MyComponent/>, document.getElementById('content')
);
复制代码

# 18. React 中的箭头函数是什么?怎么用?

箭头函数(=>)是用于编写函数表达式的简短语法。这些函数允许正确绑定组件的上下文,因为在 ES6 中默认下不能使用自动绑定。使用高阶函数时,箭头函数非常有用。

//General way
render() {
    return(
        <MyInput onChange = {this.handleChange.bind(this) } />
    );
}
//With Arrow Function
render() {
    return(
        <MyInput onChange = { (e)=>this.handleOnChange(e) } />
    );
}
复制代码

# 19. 区分有状态和无状态组件。

有状态组件 无状态组件
1. 在内存中存储有关组件状态变化的信息 1. 计算组件的内部的状态
2. 有权改变状态 2. 无权改变状态
3. 包含过去、现在和未来可能的状态变化情况 3. 不包含过去,现在和未来可能发生的状态变化情况
4. 接受无状态组件状态变化要求的通知,然后将 props 发送给他们。 4. 从有状态组件接收 props 并将其视为回调函数。

# 20. React 组件生命周期的阶段是什么?

React 组件的生命周期有三个不同的阶段:

  1. * 初始渲染阶段:* 这是组件即将开始其生命之旅并进入 DOM 的阶段。
  2. * 更新阶段:* 一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
  3. * 卸载阶段:* 这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。

# 21. 详细解释 React 组件的生命周期方法。

一些最重要的生命周期方法是:

  1. *componentWillMount***()** – 在渲染之前执行,在客户端和服务器端都会执行。
  2. *componentDidMount***()** – 仅在第一次渲染后在客户端执行。
  3. *componentWillReceiveProps***()** – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
  4. *shouldComponentUpdate***()** – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回 true 否则返回 **false**。默认情况下,它返回 false。
  5. *componentWillUpdate***()** – 在 DOM 中进行渲染之前调用。
  6. *componentDidUpdate***()** – 在渲染发生后立即调用。
  7. *componentWillUnmount***()** – 从 DOM 卸载组件后调用。用于清理内存空间。

# 22. React 中的事件是什么?

在 React 中,事件是对鼠标悬停、鼠标单击、按键等特定操作的触发反应。处理这些事件类似于处理 DOM 元素中的事件。但是有一些语法差异,如:

  1. 用驼峰命名法对事件命名而不是仅使用小写字母。
  2. 事件作为函数而不是字符串传递。

事件参数重包含一组特定于事件的属性。每个事件类型都包含自己的属性和行为,只能通过其事件处理程序访问。

# 23. 如何在 React 中创建一个事件?

class Display extends React.Component({
    show(evt) {
        // code
    },
    render() {
        // Render the div with an onClick prop (value is a function)
        return (
            <div onClick={this.show}>Click Me!</div>
        );
    }
});
复制代码

# 24. React 中的合成事件是什么?

合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性。

# 25. 你对 React 的 refs 有什么了解?

Refs 是 React 中引用的简写。它是一个有助于存储对特定的 React 元素或组件的引用的属性,它将由组件渲染配置函数返回。用于对 render () 返回的特定元素或组件的引用。当需要进行 DOM 测量或向组件添加方法时,它们会派上用场。

class ReferenceDemo extends React.Component{
     display() {
         const name = this.inputDemo.value;
         document.getElementById('disp').innerHTML = name;
     }
render() {
    return(
          <div>
            Name: <input type="text" ref={input => this.inputDemo = input} />
            <button name="Click" onClick={this.display}>Click</button>
            <h2>Hello <span id="disp"></span> !!!</h2>
          </div>
    );
   }
 }
复制代码

# 26. 列出一些应该使用 Refs 的情况。

以下是应该使用 refs 的情况:

  • 需要管理焦点、选择文本或媒体播放时
  • 触发式动画
  • 与第三方 DOM 库集成

# 27. 如何模块化 React 中的代码?

可以使用 export 和 import 属性来模块化代码。它们有助于在不同的文件中单独编写组件。

//ChildComponent.jsx
export default class ChildComponent extends React.Component {
    render() {
        return(
              <div>
              	<h1>This is a child component</h1>
              </div>
        );
    }
}

//ParentComponent.jsx
import ChildComponent from './childcomponent.js';
class ParentComponent extends React.Component {
    render() {
        return(
             <div>
                <App />
             </div>
        );
    }
}
复制代码

# 28. 如何在 React 中创建表单

React 表单类似于 HTML 表单。但是在 React 中,状态包含在组件的 state 属性中,并且只能通过 setState() 更新。因此元素不能直接更新它们的状态,它们的提交是由 JavaScript 函数处理的。此函数可以完全访问用户输入到表单的数据。

handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
}

render() {
    return (
        <form onSubmit={this.handleSubmit}>
            <label>
                Name:
                <input type="text" value={this.state.value} onChange={this.handleSubmit} />
            </label>
            <input type="submit" value="Submit" />
        </form>
    );
}
复制代码

# 29. 你对受控组件和非受控组件了解多少?

受控组件 非受控组件
1. 没有维持自己的状态 1. 保持着自己的状态
2. 数据由父组件控制 2. 数据由 DOM 控制
3. 通过 props 获取当前值,然后通过回调通知更改 3. Refs 用于获取其当前值

# 30. 什么是高阶组件(HOC)?

高阶组件是重用组件逻辑的高级方法,是一种源于 React 的组件模式。 HOC 是自定义组件,在它之内包含另一个组件。它们可以接受子组件提供的任何动态,但不会修改或复制其输入组件中的任何行为。你可以认为 HOC 是 “纯(Pure)” 组件。

# 31. 你能用 HOC 做什么?

HOC 可用于许多任务,例如:

  • 代码重用,逻辑和引导抽象
  • 渲染劫持
  • 状态抽象和控制
  • Props 控制

# 32. 什么是纯组件?

纯(Pure) 组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能。

# 33. React 中 key 的重要性是什么?

key 用于识别唯一的 Virtual DOM 元素及其驱动 UI 的相应数据。它们通过回收 DOM 中当前所有的元素来帮助 React 优化渲染。这些 key 必须是唯一的数字或字符串,React 只是重新排序元素而不是重新渲染它们。这可以提高应用程序的性能。

# React Redux

# 34. MVC 框架的主要问题是什么?

以下是 MVC 框架的一些主要问题:

  • 对 DOM 操作的代价非常高
  • 程序运行缓慢且效率低下
  • 内存浪费严重
  • 由于循环依赖性,组件模型需要围绕 models 和 views 进行创建

# 35. 解释一下 Flux

flux

Flux 是一种强制单向数据流的架构模式。它控制派生数据,并使用具有所有数据权限的中心 store 实现多个组件之间的通信。整个应用中的数据更新必须只能在此处进行。 Flux 为应用提供稳定性并减少运行时的错误。

# 36. 什么是 Redux?

Redux 是当今最热门的前端开发库之一。它是 JavaScript 程序的可预测状态容器,用于整个应用的状态管理。使用 Redux 开发的应用易于测试,可以在不同环境中运行,并显示一致的行为。

# 37. Redux 遵循的三个原则是什么?

  1. ** 单一事实来源:** 整个应用的状态存储在单个 store 中的对象 / 状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
  2. ** 状态是只读的:** 改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
  3. ** 使用纯函数进行更改:** 为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。

Store

# 38. 你对 “单一事实来源” 有什么理解?

Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。

# 39. 列出 Redux 的组件。

Redux 由以下组件组成:

  1. Action – 这是一个用来描述发生了什么事情的对象。
  2. Reducer – 这是一个确定状态将如何变化的地方。
  3. Store – 整个程序的状态 / 对象树保存在 Store 中。
  4. View – 只显示 Store 提供的数据。

# 40. 数据如何通过 Redux 流动?

Data Flow in Redux

# 41. 如何在 Redux 中定义 Action?

React 中的 Action 必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,并且还可以向其添加更多的属性。在 Redux 中,action 被名为 Action Creators 的函数所创建。以下是 Action 和 Action Creator 的示例:

function addTodo(text) {
       return {
                type: ADD_TODO,
                 text
    }
}
复制代码

# 42. 解释 Reducer 的作用。

Reducers 是纯函数,它规定应用程序的状态怎样因响应 ACTION 而改变。Reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态。

# 43. Store 在 Redux 中的意义是什么?

Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态 / 对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。

# 44. Redux 与 Flux 有何不同?

Flux Redux
1. Store 包含状态和更改逻辑 1. Store 和更改逻辑是分开的
2. 有多个 Store 2. 只有一个 Store
3. 所有 Store 都互不影响且是平级的 3. 带有分层 reducer 的单一 Store
4. 有单一调度器 4. 没有调度器的概念
5. React 组件订阅 store 5. 容器组件是有联系的
6. 状态是可变的 6. 状态是不可改变的

# 45. Redux 有哪些优点?

Redux 的优点如下:

  • 结果的可预测性 - 由于总是存在一个真实来源,即 store ,因此不存在如何将当前状态与动作和应用的其他部分同步的问题。
  • 可维护性 - 代码变得更容易维护,具有可预测的结果和严格的结构。
  • 服务器端渲染 - 你只需将服务器上创建的 store 传到客户端即可。这对初始渲染非常有用,并且可以优化应用性能,从而提供更好的用户体验。
  • 开发人员工具 - 从操作到状态更改,开发人员可以实时跟踪应用中发生的所有事情。
  • 社区和生态系统 - Redux 背后有一个巨大的社区,这使得它更加迷人。一个由才华横溢的人组成的大型社区为库的改进做出了贡献,并开发了各种应用。
  • 易于测试 - Redux 的代码主要是小巧、纯粹和独立的功能。这使代码可测试且独立。
  • 组织 - Redux 准确地说明了代码的组织方式,这使得代码在团队使用时更加一致和简单。

# React 路由

# 46. 什么是 React 路由?

React 路由是一个构建在 React 之上的强大的路由库,它有助于向应用程序添加新的屏幕和流。这使 URL 与网页上显示的数据保持同步。它负责维护标准化的结构和行为,并用于开发单页 Web 应用。 React 路由有一个简单的 API。

# 47. 为什么 React Router v4 中使用 switch 关键字 ?

虽然 <div> ** 用于封装 Router 中的多个路由,当你想要仅显示要在多个定义的路线中呈现的单个路线时,可以使用 “switch” 关键字。使用时, <switch> ** 标记会按顺序将已定义的 URL 与已定义的路由进行匹配。找到第一个匹配项后,它将渲染指定的路径。从而绕过其它路线。

# 48. 为什么需要 React 中的路由?

Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。所以基本上我们需要在自己的应用中添加一个 Router 库,允许创建多个路由,每个路由都会向我们提供一个独特的视图

<switch>
    <route exact path=’/’ component={Home}/>
    <route path=’/posts/:id’ component={Newpost}/>
    <route path=’/posts’   component={Post}/>
</switch>
复制代码

# 49. 列出 React Router 的优点。

几个优点是:

  1. 就像 React 基于组件一样,在 React Router v4 中,API 是 ‘All About Components’。可以将 Router 可视化为单个根组件( <BrowserRouter> ),其中我们将特定的子路由( <route> )包起来。
  2. 无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在 <BrowserRouter> 组件中。
  3. 包是分开的:共有三个包,分别用于 Web、Native 和 Core。这使我们应用更加紧凑。基于类似的编码风格很容易进行切换。

# 50. React Router 与常规路由有何不同?

主题 常规路由 React 路由
参与的页面 每个视图对应一个新文件 只涉及单个 HTML 页面
URL 更改 HTTP 请求被发送到服务器并且接收相应的 HTML 页面 仅更改历史记录属性
体验 用户实际在每个视图的不同页面切换 用户认为自己正在不同的页面间切换

React Hooks

如果你刚开始接触 Hook,那么可能需要先查阅 Hook 概览。你也可以在 Hooks FAQ 章节中获取有用的信息。

# 一、什么是 Hooks

  • React 一直都提倡使用 **** 函数组件 **,但是有时候需要使用 state 或者其他一些功能时,只能使用 ** 类组件 ****,因为函数组件没有实例,没有生命周期函数,只有类组件才有

  • Hooks 是 React 16.8 新增的特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

  • 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以直接在现有的函数组件中使用 Hooks

  • 凡是 use 开头的 React API 都是 Hooks

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>
  );
}

# 二、Hooks 解决的问题

# 1. 类组件的不足

  • 状态逻辑难复用: 在组件之间复用状态逻辑很难,可能要用到 render props渲染属性)或者 HOC高阶组件),但无论是渲染属性,还是高阶组件,都会在原先的组件外包裹一层父容器(一般都是 div 元素),导致层级冗余

  • 趋向复杂难以维护:

    • 在生命周期函数中混杂不相干的逻辑(如:在 componentDidMount 中注册事件以及其他的逻辑,在 componentWillUnmount 中卸载事件,这样分散不集中的写法,很容易写出 bug )
    • 类组件中到处都是对状态的访问和处理,导致组件难以拆分成更小的组件
  • this 指向问题

    :父组件给子组件传递函数时,必须绑定 this

    • react 中的组件四种绑定 this 方法的区别
class App extends React.Component<any, any> {
  handleClick2;

  constructor(props) {
    super(props);
    this.state = {
      num: 1,
      title: " react study",
    };
    this.handleClick2 = this.handleClick1.bind(this);
  }

  handleClick1() {
    this.setState({
      num: this.state.num + 1,
    });
  }

  handleClick3 = () => {
    this.setState({
      num: this.state.num + 1,
    });
  };

  render() {
    return (
      <div>
        <h2>Ann, {this.state.num}</h2>
        <button onClick={this.handleClick2}>btn1</button>
        <button onClick={this.handleClick1.bind(this)}>btn2</button>
        <button onClick={() => this.handleClick1()}>btn3</button>
        <button onClick={this.handleClick3}>btn4</button>
      </div>
    );
  }
}

前提:子组件内部做了性能优化,如(React.PureComponent

  • 第一种是在构造函数中绑定 this:那么每次父组件刷新的时候,如果传递给子组件其他的 props 值不变,那么子组件就不会刷新;
  • 第二种是在 render () 函数里面绑定 this:因为 bind 函数会返回一个新的函数,所以每次父组件刷新时,都会重新生成一个函数,即使父组件传递给子组件其他的 props 值不变,子组件每次都会刷新;
  • 第三种是使用箭头函数:父组件刷新的时候,即使两个箭头函数的函数体是一样的,都会生成一个新的箭头函数,所以子组件每次都会刷新;
  • 第四种是使用类的静态属性:原理和第一种方法差不多,比第一种更简洁

综上所述,如果不注意的话,很容易写成第三种写法,导致性能上有所损耗。

# 2. Hooks 优势

  • 能优化类组件的三大问题
  • 能在无需修改组件结构的情况下复用状态逻辑(自定义 Hooks )
  • 能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • 副作用的关注点分离副作用指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生 dom 元素、本地持久化缓存、绑定 / 解绑事件、添加订阅、设置定时器、记录日志等。以往这些副作用都是写在类组件生命周期函数中的。而 useEffect 在全部渲染完毕后才会执行, useLayoutEffect 会在浏览器 layout 之后, painting 之前执行。

# 三、注意事项

# 四、useState & useMemo & useCallback

  • React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的 **** 调用顺序 **** 是不变的。
  • 通过在函数组件里调用它来给组件添加一些内部 state,React 会 在重复渲染时保留这个 state
  • useState 唯一的参数就是初始 state
  • useState 会返回一个数组:一个 state,一个更新 state 的函数
    • 在初始化渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
    • 你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并,而是直接替换
// 这里可以任意命名,因为返回的是数组,数组解构
const [state, setState] = useState(initialState);

# 4.1 使用例子

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>
  );
}

state 初始值为 { count: 0 } ,当用户点击按钮后,我们通过调用 this.setState() 来增加 state.count 。整个章节中都将使用该 class 的代码片段做示例。

等价的 class 示例

如果你之前在 React 中使用过 class,这段代码看起来应该很熟悉:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

# 4.2 每次渲染都是独立的闭包

  • 每一次渲染都有它自己的 Props 和 State
  • 每一次渲染都有它自己的事件处理函数
  • 当点击更新状态的时候,函数组件都会重新被调用,那么每次渲染都是独立的,取到的值不会受后面操作的影响
function Counter2() {
  let [number, setNumber] = useState(0);
  function alertNumber() {
    setTimeout(() => {
      // alert 只能获取到点击按钮时的那个状态
      alert(number);
    }, 3000);
  }
  return (
    <>
      <p>{number}</p>
      <button onClick={() => setNumber(number + 1)}>+</button>
      <button onClick={alertNumber}>alertNumber</button>
    </>
  );
}

# 4.3 函数式更新

  • 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将回调函数当做参数传递给 setState。该回调函数将接收先前的 state,并返回一个更新后的值。
function Counter() {
  let [number, setNumber] = useState(0);
  function lazy() {
    setTimeout(() => {
      // setNumber(number+1);
      // 这样每次执行时都会去获取一遍 state,而不是使用点击触发时的那个 state
      setNumber((number) => number + 1);
    }, 3000);
  }
  return (
    <>
      <p>{number}</p>
      <button onClick={() => setNumber(number + 1)}>+</button>
      <button onClick={lazy}>lazy</button>
    </>
  );
}

# 4.4 惰性初始化 state

  • initialState 参数只会在组件的初始化渲染中起作用,后续渲染时会被忽略
  • 如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
function Counter5(props) {
  console.log("Counter5 render");
  // 这个函数只在初始渲染时执行一次,后续更新状态重新渲染组件时,该函数就不会再被调用
  function getInitState() {
    return { number: props.number };
  }
  let [counter, setCounter] = useState(getInitState);
  return (
    <>
      <p>{counter.number}</p>
      <button onClick={() => setCounter({ number: counter.number + 1 })}>
        +
      </button>
      <button onClick={() => setCounter(counter)}>setCounter</button>
    </>
  );
}

# 4.5 性能优化

# 4.5.1 Object.is (浅比较)

  • Hook 内部使用 Object.is 来比较新 / 旧 state 是否相等
  • 与 class 组件中的 setState 方法不同,如果你修改状态的时候,传的状态值没有变化,则不重新渲染
  • 与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果
function Counter() {
  const [counter, setCounter] = useState({ name: "计数器", number: 0 });
  console.log("render Counter");
  // 如果你修改状态的时候,传的状态值没有变化,则不重新渲染
  return (
    <>
      <p>
        {counter.name}:{counter.number}
      </p>
      <button
        onClick={() => setCounter({ ...counter, number: counter.number + 1 })}
      >
        +
      </button>
      <button onClick={() => setCounter(counter)}>++</button>
    </>
  );
}
j;

# 4.5.2 减少渲染次数

  • 默认情况,只要父组件状态变了(不管子组件依不依赖该状态),子组件也会重新渲染
  • 一般的优化:
    1. 类组件:可以使用 pureComponent
    2. 函数组件:使用 React.memo ,将函数组件传递给 memo 之后,就会返回一个新的组件,新组件的功能:如果接受到的属性不变,则不重新渲染函数
  • 但是怎么保证属性不会变尼?这里使用 useState ,每次更新都是独立的const [number,setNumber] = useState(0) 也就是说每次都会生成一个新的值(哪怕这个值没有变化),即使使用了 React.memo ,也还是会重新渲染
import React, { useState, memo, useMemo, useCallback } from "react";

function SubCounter({ onClick, data }) {
  console.log("SubCounter render");
  return <button onClick={onClick}>{data.number}</button>;
}
SubCounter = memo(SubCounter);
export default function Counter6() {
  console.log("Counter render");
  const [name, setName] = useState("计数器");
  const [number, setNumber] = useState(0);
  const data = { number };
  const addClick = () => {
    setNumber(number + 1);
  };
  return (
    <>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <SubCounter data={data} onClick={addClick} />
    </>
  );
}
  • 更深入的优化:
    1. useCallback:接收一个内联回调函数参数和一个依赖项数组(子组件依赖父组件的状态,即子组件会使用到父组件的值) ,useCallback 会返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
    2. useMemo:把创建函数和依赖项数组作为参数传入 useMemo ,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算

# 基础 Hook

# useState

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

const Hook = () => {
  const [count, setCount] = useState({
    name: "zc",
    age: 18,
  });
  const [arr, setArr] = useState([1, 2, 3]);
  const [func, setFunc] = useState(() => {
    return 1;
  });

  const handle = () => {
    setCount({
      ...count,
      age: count.age + 1,
    });
  };
  return (
    <>
      <h1>{count.name}</h1>
      <h1>{count.age}</h1>
      <h1>{arr}</h1>
      <h1>{func}</h1>
      <button onClick={handle}>增加</button>
      <button
        onClick={() => {
          setArr(() => {
            arr.push(4);
            return [...arr];
          });
        }}
      >
        增加
      </button>
    </>
  );
};

export default Hook;

# useEffect

  • effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生 dom 元素、本地持久化缓存、绑定 / 解绑事件、添加订阅、设置定时器、记录日志等。
  • 副作用操作 **** 可以分两 **** 类:**** 需要清除的和不需要清除的 ****。
  • 原先在函数组件内(这里指在 React 渲染阶段)改变 dom 、发送 ajax 请求以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API
  • useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
  • componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
import React from "react";
import { useEffect, useState } from "react";

const Hook1 = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(1);
  }, []); // []不监听任何状态

  return (
    <div>
      <h1>useEffect</h1>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
};

export default Hook1;

清除 effect

通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点, useEffect 函数需返回一个清除函数。以下就是一个创建订阅的例子:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});

# useContext

useContext & createContext. 一起使用

  1. 先使用 createContext 创建父容器

  2. 暴露给子组建

    <MyContext.Provider value={count}>
      <Comson />
    </MyContext.Provider>
  3. 子组件使用 useContext 接受父容器后使用 const value = useContext(MyContext);

全局变量,跨级传值

const value = useContext(MyContext);

接收一个 context 对象( React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo shouldComponentUpdate ,也会在组件本身使用 useContext 时重新渲染。

别忘记 useContext 的参数必须是 context 对象本身

  • 正确: useContext(MyContext)
  • 错误: useContext(MyContext.Consumer)
  • 错误: useContext(MyContext.Provider)

调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化

import React, { useContext, useState, createContext } from "react";

const MyContext = createContext();

const Comson = () => {
  const count = useContext(MyContext);
  return (
    <>
      <h1>子组件</h1>
      <p>我是子组件 ---- {count}</p>
    </>
  );
};

const HookuseContext = () => {
  const [count, setCount] = useState(0);

  const handle = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h1>{count}</h1>
      <MyContext.Provider value={count}>
        <Comson />
      </MyContext.Provider>
      <button onClick={handle}>点击</button>
    </div>
  );
};

export default HookuseContext;

提示

如果你在接触 Hook 前已经对 context API 比较熟悉,那应该可以理解, useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>

useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。

# 额外的 Hook

以下介绍的 Hook,有些是上一节中基础 Hook 的变体,有些则仅在特殊情况下会用到。不用特意预先学习它们。

# useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数( initialValue )。返回的 ref 对象在组件的整个生命周期内保持不变。

一个常见的用例便是命令式地访问子组件:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

本质上, useRef 就像是可以在其 .current 属性中保存一个可变值的 “盒子”。

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而, useRef()ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是, useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时, useRef不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现

# useMemo

执行时机不同,**useEffect ** 是在 componentDidMount 以后执行的,而 useMemo 是在组件渲染过程中执行的。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 值。

把 “创建” 函数和依赖项数组作为参数传入 useMemo ,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组, useMemo 在每次渲染时都会计算新的值。

** 你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。** 将来,React 可能会选择 “遗忘” 以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo ,以达到优化性能的目的。

注意

依赖项数组不会作为参数传给 “创建” 函数。虽然从概念上来说它表现为:所有 “创建” 函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

import React, { useMemo, useEffect, useState } from "react";
const Hook2 = () => {
  const [count, setCount] = useState(0);

  let res = useMemo(() => {
    return count;
  }, []);
  
  const handle = () => {
    setCount(count + 1);
  };
  
  return (
    <div>
      <div>{count}</div>
      <p>{res}</p>

      <button onClick={handle}>点击</button>
    </div>
  );
};

export default Hook2;

# useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

返回一个 memoized 回调函数。

把内联回调函数及依赖项数组作为参数传入 useCallback ,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate )的子组件时,它将非常有用。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

注意

依赖项数组不会作为参数传给回调函数。虽然从概念上来说它表现为:所有回调函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

import React from "react";
import { useCallback } from "react";
import { useState } from "react";

const HookUseCallbak = () => {
  const [count, setCount] = useState(0);

  let callback = useCallback(() => {
    console.log("callback");
    return count;
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <h2>{callback()}</h2>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
};

export default HookUseCallbak; 

# forwardRef

配合 useRef 获取子组件的 ref 引用

import React, { useRef, forwardRef } from "react";

const Com = forwardRef((props, ref) => {
  return (
    <div>
      <p ref={ref}> 我是子组建</p>
    </div>
  );
});

const HookUseCallbak = () => {
  const refdemo = useRef(null);
  return (
    <div>
      <h1>213</h1>
      <Com ref={refdemo} />

      <button onClick={() => console.log(refdemo)}>点击</button>
    </div>
  );
};

export default HookUseCallbak;

# useImperativeHandle

自定义 暴露~

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref自定义暴露给父组件的实例值 。在大多数情况下,应当避免使用 ref 这样的命令式代码。 useImperativeHandle 应当与 forwardRef 一起使用:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()

import React, { useRef, forwardRef } from "react";
import { useImperativeHandle } from "react";

const Com = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    name: "zc",
    add:()=>{
        console.log('111');
    }
  }));

  return (
    <div>
      <p> 我是子组建</p>
    </div>
  );
});

const HookUseCallbak = () => {
  const refdemo = useRef(null);
  return (
    <div>
      <h1>213</h1>
      <Com ref={refdemo} />

      <button onClick={() => console.log(refdemo)}>点击</button>
    </div>
  );
};

export default HookUseCallbak;

# useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前, useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

import React from "react";
import { useEffect } from "react";
import { useLayoutEffect } from "react";
import { useState } from "react";

const HookuseLayoutEffect = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("useEffect");
    return () => {
      console.log("useEffect-return");
    };
  });

  // useEffect 是在 componentDidMount 以后执行的,useLayoutEffect在浏览器执行绘制之前执行(会阻塞组件挂载,慎用)
  // 慎用 -- 会阻塞组件的挂载
  useLayoutEffect(() => {
    console.log("useLayoutEffect");
    return () => {
      console.log("useLayoutEffect-return");
    };
  });

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(2)}>点击</button>
    </div>
  );
};

export default HookuseLayoutEffect;

MLNOuG

# 自定义 Hook(*)

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

  1. 自定义的 hook,必须以 use 开头
  2. 自定义的 hook,可以使用这些 hook(useSate,useEffect…)来封装
import React, { useState } from "react";

const useCus = (val, num) => {
  const [count, setCount] = useState(val);

  const add = () => {
    setCount(count + num);
  };

  return {
    count,
    add,
  };
};

const Hook3 = () => {
  let { count, add } = useCus(10, 2);

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => add()}>点击</button>
    </div>
  );
};

export default Hook3;

useInstance.ts

import { useRef, useCallback, useEffect } from "react";

export default function useInstance<P extends {}, S extends {}, C extends {}>(props: P, state: S, custom?: C ) {
  const instanceRef = useRef({
    props,
    state,
    custom,
  });

  useEffect(() => {
    instanceRef.current.props = props;
    instanceRef.current.state = state;
    instanceRef.current.custom = custom;
  });

  const getProps = useCallback(():P => {
    return instanceRef.current.props;
  }, [instanceRef]);

  const getState = useCallback(():S => {
    return instanceRef.current.state;
  }, [instanceRef]);

  const getCustom = useCallback(() => {
    return instanceRef.current.custom;
  }, [instanceRef]);

  return { getProps, getState, getCustom }
}

useTimeout.ts

import { useRef, useCallback, useEffect } from "react";

export default function useTimeout() {
  const timeoutRef = useRef<null | number>(null);

  useEffect(() => {
    return () => {
      if (timeoutRef.current !== null) {
        window.clearTimeout(timeoutRef.current);
      }
    }
  }, [timeoutRef]);

  const setTimeout = useCallback((func, delay) => {
    if (timeoutRef.current !== null) {
      window.clearTimeout(timeoutRef.current);
    }
    timeoutRef.current = window.setTimeout(() => {
      timeoutRef.current = null;
      func();
    }, delay);
  }, [timeoutRef]);

  const clearTimeout = useCallback((func, delay) => {
    if (timeoutRef.current !== null) {
      window.clearTimeout(timeoutRef.current);
    }
  }, [timeoutRef]);

  return [setTimeout, clearTimeout];
}

useCacheBind.js

import { useRef, useCallback } from "react";

export default function useCachebind() {
  const cacheBindRef = useRef<any>({});

  const getCacheBind = useCallback((name: string, fn: Function, ...aArgs: any[]) => {
    const cachedBind = cacheBindRef.current;
    let cacheKey = `${name}_${aArgs.join('_')}`;
    let cache = cachedBind[cacheKey];
    if (cache) {
      let argsEqual = cache.oriFn === fn;
      for (let i = 0; i < cache.args.length; i++) {
        if (cache.args[i] !== aArgs[i]) {
          argsEqual = false;
          break;
        }
      }
      if (argsEqual) {
        return cache.fn;
      }
    }

    cache = {
      fn: fn.bind(null, ...aArgs),
      oriFn: fn,
      args: aArgs
    };
    cachedBind[cacheKey] = cache;


    return cache.fn;
  }, [cacheBindRef]);
  return { getCacheBind };
}

Create-react-app基本使用

# Create-react-app 安装方法

Create React App 是一个官方支持的创建 React 单页应用程序的方法。它提供了一个零配置的现代构建设置。

无需 安装或配置 Webpack 或 Babel 等工具。 它们是预先配置好并且隐藏的,因此你可以专注于代码。

# 快速开始

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

# 创建应用程序

你需要在本地开发计算机上使用 Node >= 6(但在服务器上不需要)。 你可以使用 nvm (macOS/Linux) 或 nvm-windows 轻松地在不同项目之间切换 Node 版本。

要创建新应用,你可以选择以下方法之一:

# npx

npx create-react-app my-app

(npx 来自 npm 5.2+ 或更高版本,查看 npm 旧版本的说明)

# npm

npm init react-app my-app

npm init <initializer> 在 npm 6+ 中可用

# Yarn

yarn create react-app my-app

yarn create 在 Yarn 0.25+ 中可用

# 图片加载

import React, { Component } from "react";
import img1 from '../src/assets/1.jpg'

class App extends Component {
  render() {
    return (
      <div>
        <img src={img1} />
        <img src={require('../src/assets/1.jpg')} alt=""/>
      </div>
    );
  }
}

export default App;

# 父子间传值

import React, { Component } from "react";
import img1 from "../src/assets/1.jpg";
import Home from "./Home";

class App extends Component {
  constructor(props) {
    super();
    this.state = {
      num: 1,
    };
  }

  onClickHandle = (props) => {
    console.log(props);
    this.setState({
      num: this.state.num+1,
    });
  };
  render() {
    return (
      <div>
        <img src={img1} />
        <img src={require("../src/assets/1.jpg")} alt="" />
        <Home msg={this.state.num} onClickHandle={this.onClickHandle} />
      </div>
    );
  }
}

export default App;
import React, { Component } from 'react';

class Home extends Component {
    render() {
        return (
            <div>
                
                <h1>{this.props.msg}</h1>
                <button onClick = {this.props.onClickHandle.bind(this,'xxx')}>点击</button>
            </div>
        );
    }
}

export default Home;

# 同级组件传值

pubsub – - 发布 / 订阅库

# 前言

  1. Pubsubjs 是一个用 JavaScript 编写的基于主题的发布 / 订阅库。
  2. Pubsubjs 具有同步解耦功能,因此主题是异步发布的。 这有助于保持程序的可预测性,因为在使用者处理主题时,主题的发起者不会被阻塞。
  3. Pubsubjs 被设计为在单个进程中使用,并不适合多进程应用程序 (比如具有许多子进程的 Node.js-Cluster)。 如果你的 Node.js 应用程序是一个单进程应用程序,你很好。 如果它是 (或将是) 一个多进程应用程序,你最好使用 redis pub /sub 或类似的应用程序

# PubSub 使用方式

  1. react 导入库: npm install pubsub-js --save
  2. react 页面引入 PubSub : import PubSub from 'pubsub-js'
  3. pubsubjs 使用 发送消息:PubSub.publish (名称,参数) 订阅消息:PubSub.subscrib (名称,函数) 取消订阅:PubSub.unsubscrib (名称)
// home.jsx  --  传递数据
import React, { Component } from "react";
import PubSub from "pubsub-js";

class Home extends Component {
  pubsub() {
    PubSub.publish("evt", 1);
  }
  render() {
    return (
      <div>
        <button onClick={this.pubsub.bind(this)}>进行同级数据传递</button>
      </div>
    );
  }
}

export default Home;
// Hello.jsx   --- 监听数据
import React, { Component } from "react";
import PubSub from "pubsub-js";

class Hello extends Component {
  constructor(props) {
    super(props);
    PubSub.subscribe("evt", (msg, data) => {
      console.log(msg);
      console.log(data);
    });
  }

  render() {
    return (
      <div>
        <h1>hello world</h1>
      </div>
    );
  }
}

export default Hello;

# 数据请求 axios 和 模拟数据 json-server

  • json-server : npm install json-server -g
  • axios : npm install --save axios

# json-server 的使用

  1. 首先准备一个 json 文件

    {
        "data1": [{
                "id": "001",
                "name": "Sherry",
                "age": 24,
                "friends": [{
                        "id": "100",
                        "name": "friend1"
                    },
                    {
                        "id": "200",
                        "name": "friend2"
                    }
                ]
            },
            {
                "id": "002",
                "name": "Addy",
                "age": 26
            }
        ],
        "data2": {
            "id": "003",
            "name": "Jack",
            "age": 25
        },
        "data3": [{
            "id": "004",
            "name": "Rebeca",
            "age": 27
        }]
    }
  2. 使用全局 json-server 命令,启动 mock 服务。这个 mock 服务,管理的数据,就是 db.json。

    json-server --watch --port 3001 db.json
  3. 使用 json-server 支持的功能,尝试进行数据访问

# 请求 mock 模拟数据 Demo

import React, { Component } from "react";
import axios from "axios";

class Test extends Component {
  componentDidMount() {
    this.ajaxFun();
  }

  ajaxFun = () => {
    axios.get("http://localhost:4000/data1").then((data) => {
      console.log(data);
    });
  };
  render() {
    return (
      <div>
        <div>
          <h1>Test</h1>
        </div>
      </div>
    );
  }
}

export default Test;

1BiO40

# 跨域

正向代理 — 开发环境

反向代理 – 上线环境

# 反向代理和正向代理

[编辑](javascript:😉

(1) 正向代理和代理服务器

正向代理即通常所说的代理,用于代表内部网络用户向 Internet 上的服务器 (或称外部服务器,通常为 Web 服务器) 发出连接请求,并接收响应结果,执行该代理功能的服务器称为代理服务器。使用代理服务器访问外部网络时,客户端必须在局域网设置中指明代理服务器的地址以及要代理的服务的端口号。 [5]

(2) 反向代理和代理服务器

反向代理的方向与正向代理相反,指代表外部网络用户向内部服务器发出请求,即接收来自 Internet 上用户的连接请求,并将这些请求转发给内部网络上的服务器,然后将从内部服务器上得到的响应返回给 Internet 上请求连接的客户:执行反向代理服务的服务器称为反向代理服务器,反向代理服务器对外部用户表现为一个服务器

# 正向代理

正向代理,意思是一个位于客户端和原始服务器 (origin server) 之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标 (原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

正向代理:是一个位于客户端和原始服务器 (origin server) 之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标 (原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

正向代理的典型用途是为在防火墙内的局域网客户端提供访问 Internet 的途径。正向代理还可以使用缓冲特性 (由 mod_cache 提供) 减少网络使用率。

使用 ProxyRequests 指令即可激活正向代理。因为正向代理允许客户端通过它访问任意网站并且隐藏客户端自身,因此你必须采取安全措施以确保仅为经过授权的客户端提供服务。

反向代理不同之处在于,典型的正向代理是一种最终用户知道并主动使用的代理方式。例如 Chrome 浏览器中安装了 switchysharp 以后,通过 switchysharp 方便地进行代理转发服务。而为此用户必须要提前在 switchysharp 中做好设置才能达到相应的效果。

# 反向代理

反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为 Web 加速,即使用反向代理作为 Web 服务器的前置机来降低网络和服务器的负载,提高访问效率。 [1]

优点

  1. 提高了内部服务器的安全

外部网络用户通过反向代理访向内部服务器,只能看到反向代理服务器的 IP 地址和端口号,内部服务器对于外部网络来说是完全不可见。而且反向代理服务器上没有保存任何的信息资源,所有的网页程序都保存在内部服务器上,对反向代理服务器的攻击并不能使真的网页信息系统受到破坏,这样就提高了内部服务器的安全性。

  1. 加快了对内部服务器的访问速度

在内部服务器前放置两台反向代理服务器,分别连接到教育网和公网,这样公网用户就可以直接通过公网线路访问学校服务器,从而避开了公网和教育网之间拥挤的链路。同时反向代理服务器的缓存功能也加快了用户的访问速度。 [4]

  1. 节约了有限的 IP 资源

校园网内部服务器除使用教育网地址外,也会采用公网的 IP 地址对外提供服务,公网分配的 IP 地址数目是有限的,如果每个服务器有分配 - 个公网地址,那是不可能的,通过反向代理技术很好的解决了 IP 地址不足的问题。

# 模拟请求真实的数据接口 中国天气网中的数据

  1. 找到配置文件 react-demo/node_modules/react-scripts

  2. 找到中国天气网的接口。: http://www.weather.com.cn/data/cityinfo/101320101.html

  3. 更改 react 项目的配置文件: react-demo/node_modules/react-scripts/config/webpackDevServer.config.js

    // 112行proxy
    proxy: {
      "/api": {
        target: "http://www.weather.com.cn/data/cityinfo/",  //对应自己的接口
          changeOrigin: true,
            "pathRewrite": {
              "^/api": "/"
            }
      }
    },
  4. react 组件:Test.jsx

    import React, { Component } from "react";
    import axios from "axios";
    
    class Test extends Component {
      componentDidMount() {
        this.ajaxFun();
      }
    
      ajaxFun = () => {
        axios.get('/api/101320101.html').then((data) => {
          console.log(data);
        });
      };
      render() {
        return (
          <div>
            <div>
              <h1>Test</h1>
            </div>
          </div>
        );
      }
    }
    
    export default Test;

    DsGJME

# react 路由

路由 — 根据 url 的不同来切换对应的组件。 实现 SPA 切换时不会刷新页面

自从 React 16 发布后, React Router 也发布了第五个版本,更好的支持 React 16。

官方文档链接:reacttraining.com/react-route…

React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。

# react-router 与 react-router-dom 区别

import { Switch, Route, Router } from 'react-router';

import { Swtich, Route, BrowserRouter, HashHistory, Link } from 'react-router-dom';
  1. # api 方面

    React-router:

    提供了路由的核心 api。如 Router、Route、Switch 等,但没有提供有关 dom 操作进行路由跳转的 api;

    React-router-dom:

    提供了 BrowserRouter、Route、Link 等 api,可以通过 dom 操作触发事件控制路由。

    Link 组件,会渲染一个 a 标签;BrowserRouter 和 HashRouter 组件,前者使用 pushState 和 popState 事件构建路由,后者使用 hash 和 hashchange 事件构建路由。

  2. # 动态路由跳转

    React-router

    router4.0 以上 this.props.history.push (’/path’) 实现跳转;

    router3.0 以上 this.props.router.push (’/path’) 实现跳转;

    React-router-dom

    直接用 this.props.history.push (’/path’) 实现跳转

  3. # 使用区别

    react-router-dom 在 react-router 的基础上扩展了可操作 dom 的 api。

    Swtich 和 Route 都是从 react-router 中导入了相应的组件并重新导出,没做什么特殊处理。

    react-router-dom 中 package.json 依赖中存在对 react-router 的依赖,故此,不需要 npm 安装 react-router。

# 路由器

每个 React Router 应用程序的核心应该是路由器组件。对于 web 项目,react-router-dom 提供 BrowserRouter 和 HashRouter 路由器。两者之间的主要区别是它们存储 URL 和与 Web 服务器通信的方式。

  • BrowserRouter 使用常规 URL 路径,创建一个像 example.com/some/path 这样真实的 URL ,但是他们要求正确的配置服务器。具体来说,您的 web 服务器需要在所有由 React Router 客户端管理的 URL 上处理相同的页面。Create React App 在开发中即开即用地支持此功能,并附带有关如何配置生产服务器的说明。
  • HashRouter 将当前位置存储在 URL 的哈希部分中,因此 URL 看起来类似于 http://example.com/#/your/page。由于哈希从不发送到服务器,因此这意味着不需要特殊的服务器配置。

# 安装

  • 你可以用 npm 或者 yarn 安装 React Router,由于我们在构建一个 web app,所以我们在这个文档中使用 react-router-dom
npm install react-router-dom --save

# 根目录 index.js 包裹

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import * as serviceWorker from './serviceWorker';

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

# Route 使用

<Route path='/home' component={Home}/>
<Route path='/hello' component={Hello}/>
<Route path='/test' component={Test}/>

I0RT40

<Link to='/home'>点我去home</Link> 
<Link to='/hello'>点我去hello</Link> 
<Link to='/test'>点我去test</Link> 


<Route path='/home' component={Home}/>
<Route path='/hello' component={Hello}/>
<Route path='/test' component={Test}/>

可以动态给选中的导航增加 active 的类名

<NavLink to='/home'>点我去home  </NavLink> 
<NavLink to='/hello'>点我去hello</NavLink> 
<NavLink to='/test'>点我去test</NavLink> 

# Route 精准匹配

exact

<Route path='/' exact component={Home}/>
<Route path='/home' component={Home}/>

# Switch 防止多次渲染

<Switch>
  <Route path="/" exact component={Home} />
  <Route path="/home" component={Home} />
  <Route path="/hello" component={Hello} />
  <Route path="/test" component={Test} />
  <Route path="/test" component={Test} />
</Switch>

# 重定向

Redirect

<Redirect from='/' to='/hello' exact></Redirect>

# 路由进阶

# 高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

HOC 在 React 的第三方库中很常见,例如 Redux 的 connect 和 Relay 的 createFragmentContainer

# withRouter

高阶组件中的 withRouter, 作用是将一个组件包裹进 Route 里面,然后 react-router 的三个对象 history, location, match 就会被放进这个组件的 props 属性中.

所以 withRouter 的作用就是,如果我们某个东西不是一个 Router, 但是我们要依靠它去跳转一个页面,比如点击页面的 logo, 返回首页,这时候就可以使用 withRouter 来做.

import { Route, Link, NavLink ,Switch,Redirect,withRouter} from "react-router-dom";
// 打印 props
componentDidMount(){
    console.log(this.props);
  }
export default withRouter(App);

LFAXmD

# 监听路由变化 history

history.listen()

componentDidMount(){
  this.props.history.listen(link =>{
    console.log(link);
  })
}

gWktwn

# 编程式导航

<button onClick={()=>this.props.history.push('/test')}>点我去test</button> 

# 路由传参

# 1、params
<Route path='/path/:name' component={Path}/>
<link to="/path/2">xxx</Link>
this.props.history.push({pathname:"/path/" + name});
读取参数用:this.props.match.params.nam

优势 : 刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url 会变得长而丑陋。

# 2、query
<Route path='/query' component={Query}/>
<Link to={{ path : ' /query' , query : { name : 'sunny' }}}>
this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
读取参数用: this.props.location.query.name

优势:传参优雅,传递参数可传对象;
缺点:刷新地址栏,参数丢失

# 3、state
<Route path='/sort ' component={Sort}/>
<Link to={{ path : ' /sort ' , state : { name : 'sunny' }}}> 
this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
读取参数用: this.props.location.query.state 

优缺点同 query

<Route path='/web/departManange ' component={DepartManange}/>
<link to="web/departManange?tenantId=12121212">xxx</Link>
this.props.history.push({pathname:"/web/departManange?tenantId" + row.tenantId});
读取参数用: this.props.location.search

优缺点同 params

# 5、react Hooks 中获取路由参数的方式:

1. 通过 hooks 钩子函数

import { useHistory,useLocation,useParams,useMatch } from 'react-router-dom';
let history = useHistory();
history.push('/')

2. 通过函数 props 参数

function Home(props) {
    const location = useLocation();
    return (
        <div className='home'>
            <Banner />
        </div>
    )
}

React 高级指引

# 无障碍表单

# 标记

所有的 HTML 表单控制,例如 <input><textarea> ,都需要被标注来实现无障碍辅助功能。我们需要提供屏幕朗读器以解释性标注。

<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>

# Refs 转发

Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。最常见的案例如下所述。

简单来说 — 进行 DOM 操作 – 官方建议不要优先使用 Ref,而考虑使用 state

# react 提供 3 种方式进行 ref 使用

  1. 字符串的方式
  2. 回调函数 (推荐)
    • 就是在 dom 节点上或者组件上挂载函数,函数的形参 试 dom 节点,达到的效果和字符串是一样的,都是获取值的引用
  3. React.createRef () — react16.3 新提供的方式。

# 字符串的方式

class MyComponent extends React.Component {
  hanle(){
    console.log(this.refs.demoref);
  }
  render(){
    return(
      <div>
      <input type="text" 
      ref = 'demoref'
      placeholder='请输入...'/> 
      <button onClick = {()=> this.hanle()}>点击</button>
		</div>
		)
	}
}

# 回调函数

class MyComponent extends React.Component {
      hanle() {
        console.log(this.demoref);
      }
      render() {
        return (
          <div>
            <input
              type="text"
              ref={(input) => (this.demoref = input)}
              placeholder="请输入..."
            />

            <button onClick={() => this.hanle()}>点击</button>
          </div>
        );
      }
    }

    ReactDOM.render(<MyComponent />, document.getElementById("app"));

# React.createRef

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    this.myRef.current.style.border = '1px solid red';
  }

  render() {
    return <div ref={this.myRef}>
      <h1>123123</h1>
    </div>;
  }
}

ReactDOM.render(<MyComponent />, document.getElementById("app"));

# Fragments

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

有时,语义化的 HTML 会被破坏。比如当在 JSX 中使用 <div> 元素来实现 React 代码功能的时候,又或是在使用列表( <ol><ul><dl> )和 HTML <table> 时。 在这种情况下,我们应该使用 React Fragments 来组合各个组件

class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}

# 短语法

你可以使用一种新的,且更简短的语法来声明 Fragments。它看起来像空标签:

class Columns extends React.Component {
  render() {
    return (
      <>
        <td>Hello</td>
        <td>World</td>
      </>
    );
  }
}

# 带 key 的 Fragments

使用显式 <React.Fragment> 语法声明的片段可能具有 key。一个使用场景是将一个集合映射到一个 Fragments 数组 - 举个例子,创建一个描述列表:

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 没有`key`,React 会发出一个关键警告
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

key 是唯一可以传递给 Fragment 的属性。未来我们可能会添加对其他属性的支持,例如事件。

# Refs and the DOM

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素

在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。

# 何时使用 Refs

下面是几个适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

避免使用 refs 来做任何可以通过声明式实现来完成的事情。

举个例子,避免在 Dialog 组件里暴露 open()close() 方法,最好传递 isOpen 属性。

# 勿过度使用 Refs

你可能首先会想到使用 refs 在你的 app 中 “让事情发生”。如果是这种情况,请花一点时间,认真再考虑一下 state 属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个 state,是更恰当的。查看 状态提升 以获取更多有关示例。

# 创建 Refs

React.createRef

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

# 为 DOM 元素添加 ref

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    this.myRef.current.style.border = '1px solid red';
  }

  render() {
    return <div ref={this.myRef}>
      <h1>123123</h1>
    </div>;
  }
}

ReactDOM.render(<MyComponent />, document.getElementById("app"));

# 为 class 组件添加 Ref

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

# Refs 与函数组件

默认情况下,你不能在函数组件上使用 ref 属性,因为它们没有实例

如果要在函数组件中使用 ref ,你可以使用 forwardRef (可与 useImperativeHandle 结合使用),或者可以将该组件转化为 class 组件。

不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  const textInput = useRef(null);

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

React基本认识与环境搭建&JSX&相关语法知识点

# 资料速览

React 官网:https://reactjs.bootcss.com/docs/hello-world.html

Create React App 中文文档

快速入门帮助 GitHub

TypeScript 中文手册:[这篇快速上手指南会教你如何将 TypeScript 与 React 结合起来使用](https://typescript.bootcss.com/tutorials/react.html)

React Router 和 @ reach /router 的未来:文档https://reactrouter.com/web/api/NavLink/activeclassname-string

# react 开发环境的搭建

  1. react.js // react 核心文件
  2. react-dom.js // 渲染页面中的 DOM,必须依赖 react 核心文件
  3. babel.js // ES6 => ES5

下载

  • react 核心包 : npm i react --save

  • React-dom : npm i react-dom --save

  • Babel: npm install --save @babel/standalone : 多用于开发环境测试简单 demo:详细介绍 ;如果您在生产中使用 Babel,通常不应使用 @ babel /standalone。相反,您应该使用在 Node.js 上运行的构建系统(例如 Webpack,Rollup 或 Parcel)来提前转换 JS。 提前转换 js 的用法

但是,@ babel /standalone 有一些有效的用例:

  • 它提供了使用 Babel 进行原型制作的简便方法。使用 @ babel /standalone,您可以在 HTML 中仅使用一个简单的脚本标签就开始使用 Babel。
  • 实时编译用户提供的 JavaScript 的站点,例如 JSFiddleJS BinBabel 站点上的 REPLJSitor 等。
  • 直接嵌入 JavaScript 引擎(例如 V8)并希望使用 Babel 进行编译的应用
  • 想要使用 JavaScript 作为脚本语言来扩展应用程序本身的应用程序,包括 ES2015 提供的所有功能。
  • 其他非 Node.js 环境(ReactJS.NETruby-babel-transpilerphp-babel-transpiler 等)。

基本结构:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="node_modules/react/umd/react.development.js"></script>
    <script src="node_modules/react-dom/umd/react-dom.development.js"></script>
    <script src="node_modules/@babel/standalone/babel.min.js"></script>
</head>

<body>
    <!--  创建dom根结点 -->
    <div id="app"></div>

    <script type="text/babel">
        ReactDOM.render(<h1>hello React</h1>,document.getElementById('app'))
    </script>
</body>

</html>

NpCnFx

# jsx 注释

let word = <h1>
           {/* 这是注释 */} 
            hello world
            </h1>
        ReactDOM.render(word,document.getElementById('app'))

# JSX 多行标签需要一个父元素包裹

let word = <div>
            <h1>12313</h1> 
            <h2>123132</h2>
        </div>

# JSX 变量

<script type="text/babel">
        let first = 'hello'
        let last = 'world'
        let obj = {
            name:'zc',
            age: 18
        }
        function getName(obj){
            return `你的名字是 ${obj.name},年龄是 ${obj.age}`
        }
        let word =(
            <div>
                <h3>{first}</h3>
                <h3>{last}</h3>
                <h1>{getName(obj)}</h1>
           </div>
        )
        ReactDOM.render(word,document.getElementById('app'))
    </script>

q28oVY

# 渲染数组

let arr = [
  '哈哈哈哈哈1',
  '哈哈哈哈哈2',
  '哈哈哈哈哈3',
  '哈哈哈哈哈4',
  '哈哈哈哈哈5',
  '哈哈哈哈哈6',
  '哈哈哈哈哈7']
let word = (
  <div>
  {arr.map(item =>{
   return <p>{item}</p> 
  })}   
  </div>
)   
ReactDOM.render(word,document.getElementById('app'))

# 属性设置

let text = '点我去百度'
let linkUrl = 'https://baidu.com'
let myDom = <a className='demo' href={linkUrl}>点我</a>

注意: 在 JSX 中 不能使用 class,这个属性,因为 class 是 js 的关键字。 – 代替使用 className

# 无状态组件 (函数组件)

<body>
    <!--  创建dom根结点 -->
    <div id="app"></div>
    <script type="text/babel">
        function Com(){
            return (
                <div>
                    <div>hello world</div>
                    <div>zhou chen</div>
                </div>
                
            )
        }
        ReactDOM.render(<Com/>,document.getElementById('app'))
    </script>
</body>

# 无状态组件传递参数

Props

function Father(props){
           return(
               <div>
                   <p>我是一个无状态函数组件 -- 父亲</p>
                   <h3>{props.name}</h3>   
                   <h3>{props.age}</h3>   
                   <h3>{props.sex}</h3>   
               </div>
           )
       }

       let msg = '我是一个变量'
       let obj = {
           name:'zc',
           age: 18,
           sex : 1
       }
       ReactDOM.render(<Father {...obj}/>,document.getElementById('app'))

# 类组件传递参数

this.props.xxx

class MyCom extends React.Component{
  render(){
    return (
      <div>
        <div>hello world</div>
        <MySon/>
        <h1>{this.props.name}</h1>
        <h1>{this.props.age}</h1>
        <h1>{this.props.sex}</h1>
			</div>
)
}   
}
let msg = '我是一个变量'
let obj = {
  name:'zc',
  age: 18,
  sex : 1
}
ReactDOM.render(<MyCom {...obj}/>,document.getElementById('app'))

hThktC

# 无状态组件的 props 验证 和默认值

随着你的应用程序不断增长,你可以通过类型检查捕获大量错误。对于某些应用程序来说,你可以使用 FlowTypeScript 等 JavaScript 扩展来对整个应用程序做类型检查。但即使你不使用这些扩展,React 也内置了一些类型检查的功能。要在组件的 props 上进行类型检查,你只需配置特定的 propTypes 属性:

默认值:您可以通过配置特定的 defaultProps 属性来定义 props 的默认值: :react 版本要求 16 以后;

props 验证: propTypes 验证类型

官网用法:官网

默认值:

function Com(props){
  return (
    <div>
    <p>{props.msg}</p>
    </div>

  )
}
Com.defaultProps = {
  msg :'默认值'
}

ReactDOM.render(<Com />,document.getElementById('app'))

props 类型验证

  • 引用 prop-types 库。 — npm i prop-types --save
<script type="text/babel">
  function Com(props){
  return (
    <div>
    <p>{props.msg}</p>
    <p>{props.age}</p>
    </div>

  )
}

// 默认值
Com.defaultProps = {
  msg :'默认值'
}

// props 类型验证
Com.propTypes = {
  msg : PropTypes.number,
  age:PropTypes.number.isRequired
}

ReactDOM.render(<Com msg = {'1111'} age = {18}/>,document.getElementById('app'))
  </script>

VJA0vp

import PropTypes from 'prop-types';

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

Greeting.propTypes = {
  name: PropTypes.string
};

# 类组件的 props 验证 和默认值

默认值:您可以通过配置特定的 defaultProps 属性来定义 props 的默认值: :react 版本要求 16 以后;

props 验证: propTypes 验证类型

默认值 :( 传统写法 )

class MyCom extends React.Component{
  render(){
    return (
      <div>
        <div>hello world</div>
        <p>{this.props.name}</p>
			</div>
		)
	}   
}

// 传统写法 -- 默认值
MyCom.defaultProps = {
  name:'zc'
}
let msg = '我是一个变量'

ReactDOM.render(<MyCom />,document.getElementById('app'))

类型验证:(传统写法)

class MyCom extends React.Component{
  render(){
    return (
      <div>
        <div>hello world</div>
        <p>{this.props.name}</p>
		</div>
		)
	}   
}

MyCom.defaultProps = {
  name:'zc'
}  

MyCom.propTypes = {
  name: PropTypes.string.isRequired
}

ReactDOM.render(<MyCom name={1}/>,document.getElementById('app'))

利用 static

class MyCom extends React.Component{
          static defaultProps = {
              name:'zc'
          }

          static propTypes = {
              name: PropTypes.string.isRequired
          }

          render(){
              return (
                  <div>
                      <div>hello world</div>
                      <p>{this.props.name}</p>
                  </div>
            )
          }   
      }

# 类组件 state 状态

class Father extends React.Component{
        constructor(props){
            super(props)
            // state状态
            this.state = {
                msg : 'hello world'
            }
        }
        render(){
            return(
                <div>
                    <p>{this.state.msg}</p>    
                </div>
            )
        }

  }

   ReactDOM.render(<Father/>,document.getElementById('app'))

# setState 是异步的

class Father extends React.Component{
           constructor(props){
               super(props)
               this.state = {
                   msg : 'hello world'
               }
           }

           handle = (data)=>{
               this.setState({
                   msg:'zczczczc'
               },()=>{
                   console.log(this.state.msg);
               })    

           }
           render(){
               return(
                   <div>
                       <p>{this.state.msg}</p>    
                       <button onClick = {this.handle}>点我</button>
                   </div>
               )
           }

     }

# 字符串标签插入(字符串 => HTML)

官网说明:https://reactjs.org/docs/dom-elements.html

<div dangerouslySetInnerHTML={{__html:this.state.newHtml}}></div>

# 事件绑定 和 this

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

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

# 修改 this 指向

  1. bind 方式显示绑定
  2. 函数通过箭头函数进行创建
  3. constructor 中提前进行绑定
  4. 把事件的调用写成箭头函数的调用方式

# bind 方式显示绑定

class Com extends React.Component{
           constructor(props){
                super(props)

           }

           handle(){
               console.log(this);
           }

           render(){
               return(
                   <div>
                        <h1>121131</h1>
                        <button onClick={this.handle.bind(this)}>点击</button>
                    </div>
               )
           }
       }

# 函数通过箭头函数进行创建

class Com extends React.Component{
          constructor(props){
               super(props)

          }

          handle= ()=>{
              console.log(this);
          }

          render(){
              return(
                  <div>
                       <h1>121131</h1>
                       <button onClick={this.handle}>点击</button>
                   </div>
              )
          }
      }

# constructor 中提前进行绑定

class Com extends React.Component{
          constructor(props){
               super(props)
               this.handle = this.handle.bind(this)
          }

          handle(){
              console.log(this);
          }

          render(){
              return(
                  <div>
                       <h1>121131</h1>
                       <button onClick={this.handle}>点击</button>
                   </div>
              )
          }
      }

# 把事件的调用写成箭头函数的调用方式

class Com extends React.Component{
          constructor(props){
               super(props)
          }

          handle(){
              console.log(this);
          }

          render(){
              return(
                  <div>
                       <h1>121131</h1>
                       <button onClick={()=>this.handle()}>点击</button>
                   </div>
              )
          }
      }

# 传递参数

<button onClick={this.handle.bind(this,'xxxx')}>点击</button>

<button onClick={()=>this.handle('xxxx')}>点击</button>

# Event 事件对象

class Com extends React.Component{
           constructor(props){
                super(props)
           }

           handle(p){
               console.log(p);
               console.log(this);
           }

           render(){
               return(
                   <div>
                        <h1>121131</h1>
                        <button onClick={(e)=>this.handle(e)}>点击</button>
                    </div>
               )
           }
       }

# 条件渲染

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

React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。

# 状态提升

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

class Demoa extends React.Component {
   constructor(props) {
     super(props);
   }

   render() {
     return (
       <div>
         <h1>我的demoa ---- {this.props.msg}</h1>
       </div>
     );
   }
 }
 class Demob extends React.Component {
   constructor(props) {
     super(props);
   }

   render() {
     return (
       <div>
         <h1>我的demob  ---- {this.props.msg}</h1>
       </div>
     );
   }
 }

 class Com1 extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
         msg: '我是2个组件都用的数据'
     }
   }


   handle(){
       this.setState({
           msg:'我被修改了'
       })
   }
   render() {
     return (
       <div>
         <h1>我是组件</h1>
         <Demoa msg = {this.state.msg}/>
         <Demob msg = {this.state.msg}/>
         <button onClick = {()=>this.handle()}>修改</button>
       </div>
     );
   }
 }
 ReactDOM.render(<Com1 />, document.getElementById("app"));

AiYlXZ

# 相关 Error

jDVqvD

错误原因:组件名字首写字母要大写字母。

6dGfpO

错误原因:React.component => **React.Component. 拼写错误。

20ng4N

错误原因:class 类组件、super () 没有被调用。

Tb902w

错误原因:父组件传递过来的参数 为调用某一项~

  • Copyrights © 2015-2021 zhou chen
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信