Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yayxs opened this issue Jun 26, 2020 · 1 comment
Open

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

yayxs opened this issue Jun 26, 2020 · 1 comment

Comments

@yayxs
Copy link
Owner

yayxs commented Jun 26, 2020

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

  • 后台管理系统 admin系统 cms 系统 对数据增删查改 admin
    • 文章
    • 用户等等
  • 前台展示web 应用 面向于C端 互联网的用户都能看到

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

使用的技术

  • react技术栈
  • 语言 (语法) 这里我们采用 TypeScript

动态调整

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

代码

放在github上

开始

blog-fe-cms/

持续的来写

环境变量 十分重要

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

生产 又是不同的一套配置

环境变量 env

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

  • 我们知道, CRA 功能是什么呢 后台管理系统数据的处理

  • react 生态中 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

重点是什么呢?

Hooks

useState

  • 基于 next,js UI ====> 简单的 辅助学习 react hooks
  • 简单的去写hooks 函数 状态

setState

  • 面试 火热 一个题目
    • 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>
  }
}
  • setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的

  • setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

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

camsong/blog#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

  • 之前呢 我们的方案 是 基于CRA 写 ts
  • 更换思维 我们现在站在 tS 去考虑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 文件大致是这样的。不得不提的是

  • 我们可以安装 webpack-cli
  • 可以通过 npx webpack -w 来监听index.html 文件的修改
{
  "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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants