yayxs / front-end-video-tutorial

前端知识视频化 / 视频分享 / 视频教程
11 stars 3 forks source link

【实战】2020 基于react生态的一次博客实战探索 #13

Open yayxs opened 4 years ago

yayxs commented 4 years ago

2020 基于react生态的一次博客实战探索


暂且先准备这样,后续的接口会慢慢来

使用的技术


动态调整

一切都是动态调整的 也是一次我们共同学习的过程

代码

放在github上

开始

blog-fe-cms/

持续的来写

环境变量 十分重要

开发场景下 是需要 读 开发的一些常量配置

生产 又是不同的一套配置

环境变量 env

选择一个什么样的UI框架呢

怎么把 antd 集成到我们的项目中?

并不想去给大家直接说怎么做

思考?

部署

/www/server/nginx/conf/nginx.conf synta
events
    {
        use epoll;
        worker_connections 51200;
        multi_accept on;
    }

http
    {
        include       mime.types;
                #include luawaf.conf;

                include proxy.conf;

        default_type  application/octet-stream;

        server_names_hash_bucket_size 512;
        client_header_buffer_size 32k;
        large_client_header_buffers 4 32k;
        client_max_body_size 50m;

        sendfile   on;
        tcp_nopush on;

        keepalive_timeout 60;

        tcp_nodelay on

CSS 预编译器 less

我们是用 hooks语法 新特性

重点是什么呢?

Hooks

useState

setState

react 的 jsx 中的点击事件 是 看起来像所谓异步

 handleClick() {
    // console.log(this);
    console.log(this.state);
    this.setState({ val: this.state.val + 1 });

    console.log(this.state); // 所谓的 异步
  }
 componentDidMount() {
    console.log(this.state);
    this.setState({ val: this.state.val + 1 });
    console.log(this.state); // 像所谓的异步一样
  }
 timer: ReturnType<typeof setTimeout> = setTimeout(() => {
    console.log(this.state);
    this.setState({ val: this.state.val + 1 }); // 提供给开发者
    console.log(this.state); // 好像是 同步的一样 所谓的异步
  }, 2000);

浅拷贝 深浅拷贝 this 指向 绑定this 的方式又有什么呢 case

关于像 对象 或者是 数组 基本 aPI 实际的而开发 中 深浅拷贝 防抖节流 都会lu

class App extends React.Component {
  state = { val: 0 }
  componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)
    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val);
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)
    }, 0)
  }
  render() {
    return <div>{this.state.val}</div>
  }
}

useEffect

componentDidMount`, `componentDidUpdate`, and `componentWillUnmount

都有什么生命周期呢 ?? 面试

粒子登录背景

https://github.com/matteobruni/tsparticles

import { ComponentClass } from "react";
     8 | import { Container } from "tsparticles/dist/Core/Container";
  >  9 | import type { IOptions } from "tsparticles/dist/Options/Interfaces/IOptions";
       |             ^
    10 | import type { RecursivePartial } from "tsparticles/dist/Types/RecursivePartial";
    11 | import { IPolygonMaskOptions } from "tsparticles/dist/Plugins/PolygonMask/PolygonMaskPlugin";
    12 | import { IAbsorberOptions } from "tsparticles/dist/Plugins/Absorbers/AbsorbersPlugin";
@commitlint/cli                     8.3.5     8.3.5    9.0.1  blog-fe-cms
@commitlint/config-conventional     8.3.4     8.3.4    9.0.1  blog-fe-cms
@testing-library/jest-dom           4.2.4     4.2.4   5.11.0  blog-fe-cms
@testing-library/react              9.5.0     9.5.0   10.4.3  blog-fe-cms
@testing-library/user-event         7.2.1     7.2.1  12.0.11  blog-fe-cms
@types/jest                        24.9.1    24.9.1   26.0.3  blog-fe-cms
@types/node                      12.12.47  12.12.47  14.0.14  blog-fe-cms
typescript                          3.7.5     3.7.5    3.9.5  blog-fe-cms

更新包依赖

  1. package.json 文件所在的目录中执行 npm update 命令。
  2. 执行 npm outdated 命令。不应该有任何输出。
# npm
npm i --save react@latest
# yarn
yarn add react@latest
npm i -g yarn
npm i -g npm-check
npm-check -u
yarn upgrade-interactive  --latest

less 模块化 css

https://github.com/camsong/blog/issues/5

Warning: Prop `className` did not match. Server: "PrivateSwitchBase-input-8 MuiSwitch-input" Client: "PrivateSwitchBase-input-4 MuiSwitch-input"
    in input (created by ForwardRef(SwitchBase))
    in span (created by ForwardRef(IconButton))
    in span (created by ForwardRef(ButtonBase))
    in ForwardRef(ButtonBase) (created by WithStyles(ForwardRef(ButtonBase)))
    in WithStyles(ForwardRef(ButtonBase)) (created by ForwardRef(IconButton))
    in ForwardRef(IconButton) (created by WithStyles(ForwardRef(IconButton)))
    in WithStyles(ForwardRef(IconButton)) (created by ForwardRef(SwitchBase))
    in ForwardRef(SwitchBase) (created by WithStyles(ForwardRef(SwitchBase)))
    in WithStyles(ForwardRef(SwitchBase)) (created by ForwardRef(Switch))
    in span (created by ForwardRef(Switch))
    in ForwardRef(Switch) (created by WithStyles(ForwardRef(Switch)))
    in WithStyles(ForwardRef(Switch)) (at pages/index.tsx:57)
    in div (created by ForwardRef(Paper))
    in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
    in WithStyles(ForwardRef(Paper)) (created by ForwardRef(Card))
    in ForwardRef(Card) (created by WithStyles(ForwardRef(Card)))
    in WithStyles(ForwardRef(Card)) (at pages/index.tsx:54)
    in div (at pages/index.tsx:53)
    in index (at _app.tsx:31)
    in ThemeProvider (at _app.tsx:28)
    in MyApp
    in ErrorBoundary (created by ReactDevOverlay)
    in ReactDevOverlay (created by Container)
    in Container (created by AppContainer)
    in AppContainer
    in Root
https://github.com/mui-org/material-ui/tree/master/examples/nextjs-with-typescript
https://jsonplaceholder.typicode.com/

api 请求 数据请求 非常重要一部分

面试

像 vue 和 react 都有非常常见的一个问题 就是 key

Each child in a list should have a unique "key" prop

TypeScript 写 React

一方面呢是 继续 走hooks

一方面 中心 是在 ts

接着 写一些 工具呀 或者是通用的方法

还会涉及 面试题

前言

本篇参考 TypeScript and React

我们写react项目的打开方式有多种,那本篇我们将站在TypeScript 的角度逆向分析,我们该怎么去优雅的用ts 描述React,想必你一定会有所收获

版本

开篇我们是需要告知我们的package.json 中一些核心依赖的版本(这在不同的版本也许是不同的效果)

思路

整体思路是依着TypeScript 的基础上 然后构建一个 React 应用,这里参考 ts 以及 webpack 的 官方官方官方文档

准备开始

为了节约大家的时间,首先在阅读下方的文章之前需要 打开几个网站

然后你需要自己跟着 ts 的官网描述搭建一个 webpack + TypeScript + React.js 的初始化项目,也许https://www.typescriptlang.org/docs/handbook/react-&-webpack.html 能帮到你,简陋的 tsconfig.json 文件大致是这样的。不得不提的是

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es6",
    "jsx": "react",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "lib": ["dom", "es2015"]
  },
  "include": ["./src/**/*"]
}

文件的导入方式

这里我们可以不使用之前的方式,如下

import * as React from "react";

组件 Components

无状态组件(函数式)

js 环境下,一个简单的Button 组件,

// 绑定元素“children”隐式具有“any”类型。
// 绑定元素“handleClick”隐式具有“any”类型
const Button = ({ onClick: handleClick, children }) => (
  <button onClick={handleClick}>{children}</button>
);

这就需要我们对其进行描述,

type Props = {
  onClick(e: MouseEvent<HTMLElement>): void  // 这里是点击事件的类型 HTMLElement泛型是个ele元素
  children?: ReactNode // 而接收的children 是个react中的 node节点(也就是所谓的Dom或者组件等)
 }

const Button = ({ onClick: handleClick, children }:Props) => (
  <button onClick={handleClick}>{children}</button>
)

还记不记得,在本文的开篇我们一起说了,一些依赖包,那么@types/react 中就替我们声明了一些优雅的描述

type Props = { onClick(e: MouseEvent<HTMLElement>): void };

const Button: SFC<Props> = ({ onClick: handleClick, children }) => (
  <button onClick={handleClick}>{children}</button>
);

也就是说我们可以通过 import React, { SFC} from "react"; 其中 SFC 以及有我们的 传入的组件(这里指 children)

或者是这样的,你也可以看下下面的代码(这里我们讨论下最常见的组件属性传值)

import React from "react";
import PropTypes from "prop-types"; // 引入类型的描述

export function ChildCom({ children, title = "我将从父组件传过来" }) {
  return (
    <div>
      {title}: {children}
    </div>
  );
}

ChildCom.propTypes = {
  title: PropTypes.string,
  children: PropTypes.node.isRequired,
};
import * as React from 'react'

// 定义一个接口用来描述我们即将接收到的属性也好或者是组件节点也好
export interface ChildComProps {
  title?: string // 传递的参数 是可选的
  children: React.ReactNode // ReactNode 这里我们上边提到了
}

export function ChildCom({
  children,
  title = '我将从父组件传过来',
}: ChildComProps) {
  return (
    <div>
      {title}: {children}
    </div>
  )
}

我们接着看

export interface ITitleProps {
  title?: string;
}

const MyText = () => {
  return <>副标题</>;
};

/**
 * 此时我们的函数参数  是从泛型  FunctionComponent 推断出来的
 * 虽然看起来和第一个相似 但是 我们可以使用可选的 子组件 children
 * @param
 */
const Header: FunctionComponent<ITitleProps> = ({ title, children }) => {
  return (
    <>
      <h2>{title}</h2>
      {children}
    </>
  );
};

const App = () => {
  return (
    <>
      <hr />
      <Header title="欢迎你">ceshi</Header>
    </>
  );
};

有状态组件(calss 类)

既然是有状态的组件,或者一个开始,我们会想到计数器 时钟 ,因为案例虽小,但是足以说明我们的问题

一个计数器(此处如此美观代码参见https://juejin.im/post/5b07caf16fb9a07aa83f2977

const initialState = { clicksCount: 0 }; // 初始化
type State = Readonly<typeof initialState>; // 这里我们的state是不可直接进行修改,不是吗

/**
 * 这里我们注意:<object,State>
 * 泛型的第一个参数 一般是指的 props (props是对象类型所以咱们可以暂时 object)
 *      第二参数 一般是  状态 state
 */
class Counter extends Component<object, State> {
  readonly state: State = initialState;
  // 不要忘了 render 方法 用来渲染视图
  render() {
    const { clicksCount } = this.state;

    return (
      <>
        <button onClick={this.handleIncrement}>加加加</button>
        <button onClick={this.handleDecrement}>减减减</button>
        <p>你点击了我{clicksCount}</p>
      </>
    );
  }
  private handleIncrement = () => this.setState(incrementClicksCount);
  private handleDecrement = () => this.setState(decrementClicksCount);

}

const incrementClicksCount = (prevState: State) => ({
  clicksCount: prevState.clicksCount + 1,
});
const decrementClicksCount = (prevState: State) => ({
  clicksCount: prevState.clicksCount - 1,
});

一个简单的时钟,用来显示时间

import React, { Component } from "react"; // 导入函数组件

import * as ReactDOM from "react-dom";

/**
 * 当前时间
 */

export interface ClockState {
  time: Date;
}

/**
 * 通用参数允许我们传入 属性和状态
 */
class App extends Component<{}, { time: Date }> {
  /**
   * 设置当前时间
   */
  setNow() {
    this.setState({
      time: new Date(),
    });
  }
  /**
   * 转换时间
   */
  time2Str(time: Date) {
    return time.toLocaleTimeString();
  }
  // 在组件挂在之前 我们去设置一下时间
  componentWillMount() {
    this.setNow();
  }
  // 在组件挂在后呢 我们需要每一秒更改一下状态
  componentDidMount() {
    setInterval(() => this.setNow(), 1000);
  }
  // 然后 就是渲染 至页面
  render() {
    return (
      <>
        <p>{this.time2Str(this.state.time)}</p>
      </>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("example"));

构造函数

export interface ISimpleProps {}

class Simple extends Component<ISimpleProps, {}> {
  constructor(props: ISimpleProps) {
    super(props);
  }
}

默认属性 defaultProps

  static defaultProps = {
    msg: 'Hello everyone!'
  }

子组件

class Simple extends Component {
  render() {
    return <>123</>;
  }
}

class App extends Component {
  render() {
    return <>{this.props.children}</>;
  }
}

ReactDOM.render(
  <App>
    {" "}
    <Simple />{" "}
  </App>,
  document.getElementById("example")
);

事件

事件是关键

import React, { Component, MouseEvent } from "react"; // 导入函数组件

import * as ReactDOM from "react-dom";

export class Button extends Component {
  handleClick(event: MouseEvent) {
    console.log(event);
    event.preventDefault();
  }

  render() {
    return <button onClick={this.handleClick}>{this.props.children}</button>;
  }
}
ReactDOM.render(<Button>点击啊</Button>, document.getElementById("example"));

限制性事件处理

可以使用泛型

import React, { Component, MouseEvent } from "react"; // 导入函数组件

import * as ReactDOM from "react-dom";

export class Button extends Component {
  /*
    点击事件限定为  HTMLButton 元素类型
  */
  handleClick(event: MouseEvent<HTMLButtonElement>) {
    console.log(`按钮点击了`);
    event.preventDefault();
  }

  /* 
    泛型可以让我 联合类型 是HTMLButtonElement 或者是 HTMLAnchorElement
  */
  handleAnotherClick(event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) {
    event.preventDefault();
    alert("Yeah!");
  }

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

ReactDOM.render(<Button>点击啊</Button>, document.getElementById("example"));

类型校验

/**
 * prop-types 中有个 InferProps
 * @param
 */
function Article({ title, id }: InferProps<typeof Article.propTypes>) {
  return (
    <div className="article">
      <h1>{title}</h1>
      <span>{id}</span>
    </div>
  );
}

Article.propTypes = {
  title: PropTypes.string.isRequired,
  id: PropTypes.number.isRequired,
};
/**
 * 在ts 的环境中  id 是可选的
 */
Article.defaultProps = {
  id: 20,
};

class App extends Component {
  render() {
    return (
      <>
        <Article title="文章" id={1} />
      </>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("example"));

重心是放在处理属性上

function ArticleList({ children }: InferProps<typeof ArticleList.propTypes>) {
  return <div className="list">{children}</div>;
}

ArticleList.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]).isRequired,
};

hooks 用法

import React, { FunctionComponent, useState, FC } from "react";
import * as ReactDOM from "react-dom";

// 组件作为数字初始值
const Counter: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
  // 我们传递了一个数字
  const [clicks, setClicks] = useState(initial);
  return (
    <>
      <p>Clicks: {clicks}</p>
      <button onClick={() => setClicks(clicks + 1)}>+</button>
      <button onClick={() => setClicks(clicks - 1)}>-</button>
    </>
  );
};

const App: FC = () => {
  return (
    <>
      <Counter />
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("example"));

useState & useEffect

import React, { FunctionComponent, useState, FC, useEffect } from "react";
import * as ReactDOM from "react-dom";

const Simple: FC = () => {
  const [name, setName] = useState("yayxs");
  const [width, setWidth] = useState(0);
  useEffect(() => {
    return () => {
      document.title = `Hello ${name}`;
    };
  }, [name]);

  // 事件的派发监听
  useEffect(() => {
    const eventHandler = () => {
      setWidth(Number(window.innerWidth));
    };
    window.addEventListener("resize", eventHandler);
    return () => {
      window.removeEventListener("resize", eventHandler);
    };
  }, [name]);
  return (
    <>
      <h4>{width}</h4>
    </>
  );
};

const App: FC = () => {
  return (
    <>
      <Simple />
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("example"));

useContext

// 上下文中有个字符串类型的 属性
export const lanContext = React.createContext({ lan: "en" });

const Simple: FC = () => {
  const { lan } = useContext(lanContext);
  return (
    <>
      <h4>{lan}</h4>
    </>
  );
};

useRef

function Simple() {
  // 用null 初始化 虽然初始化 是 null 但是 尝试去寻找 HTMLInputElement 类型的元素
  const inputEl = useRef < HTMLInputElement > null;
  const handleClick = () => {
    // 如果存在的话,才使聚焦
    if (inputEl && inputEl.current) {
      inputEl.current.focus();
    }
  };
  return (
    <>
      {/* inputEl也只可与输入元素一起使用 */}
      <input ref={inputEl} type="text" />
      <button onClick={handleClick}>Focus the input</button>
    </>
  );
}

useMemo

/**
 * 我们可以通过使用useEffect 然后传入一些参数来影响函数的执行
 * useMemo做类似的事情。
 * 假设我们有计算量大的方法,并且只想在它们的参数更改时运行它们,
 * 而不是每次组件更新时都运行它们。useMemo返回记忆的结果,并且仅在参数更改时才执行回调函数。
 */

useCallback

useReducer

// 首先是类型定义

type ActionType = {
  type: "reset" | "decrement" | "increment", // 联合类型
};

type StateType = {
  count: number,
};
const initialState = { count: 0 };

function reducer(state: StateType, action: ActionType) {
  // 确保我们正确的设置相关的情况
  switch (action.type) {
    case "reset":
      return initialState;
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter({ initialCount = 0 }) {
  /**
   * 参数一 reducer函数
   * 参数二 初始状态
   */
  const [state, dispatch] = useReducer(reducer, { count: initialCount });
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </>
  );
}

Render props and child render props

Context

/**
 * 上下文API 允许在全局级别共享数据
 *  - provider 一个提供者 提供数据传入到子级
 *  - Consumer 消费者 使用传递来的数据
 */

// 1 上下文 定义类型
type ContextProps = {
  flag: boolean,
  lan: string,
  theme: string,
};

// 1 创建上下文
// export const AppContext =  React.createContext({
//   flag:true,
//   lan:'cn',
//   theme:'dark'
// })
export const AppContext = React.createContext < Partial < ContextProps >> {};
// 2 Provide context

const App = () => {
  return (
    <AppContext.Provider
      value={{
        lan: "de",
        flag: true,
        theme: "light",
      }}
    >
      <Header />
    </AppContext.Provider>
  );
};

// 3 Consume context

const Header = () => {
  return (
    <AppContext.Consumer>
      {({ flag }) => {
        if (flag) {
          return <h1>Logged in!</h1>;
        }
        return <h1>You need to sign in</h1>;
      }}
    </AppContext.Consumer>
  );
};
matteobruni commented 4 years ago

Hello @yayxs, if you are asking help about the error you need at least typescript 3.8