balala

144 阅读44分钟

React

  • 概述

    它是一个轻量级 Javascript 库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。它使用虚拟 DOM 来有效地操作 DOM。它遵循从高阶组件到低阶组件的单向数据流。

  • 什么是 JSX

    JSX 是 javascript 的语法扩展。它就像一个拥有 javascript 全部功能的模板语言。它生成 React 元素,这些元素将在 DOM 中呈现。React 建议在组件使用 JSX。在 JSX 中,我们结合了 javascript 和 HTML,并生成了可以在 DOM 中呈现的 react 元素。

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

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

  • Props 和 State

  • 生命周期

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

    对于 web 项目,react-roruter-dom 提供了 <BrowserRouter><HashRouter>两个路由组件。

    • BrowserRouter:浏览器的路由方式,也就是使用 HTML5 提供的 history API ( pushState , replaceState 和 popstate 事件) 来保持 UI 和 url 的同步。这种方式在 react 开发中是经常使用的路由方式,但是在打包后,打开会发现访问不了页面,所以需要通过配置 nginx 解决或者后台配置代理。
    • HashRouter:在路径前加入#号成为一个哈希值,Hash 模式的好处是,再也不会因为我们刷新而找不到我们的对应路径,但是链接上面会有#/。在 vue 开发中,经常使用这种方式。

    要使用路由组件,我们只需要确保它是在根组件使用,我们应该将包裹在路由组件下面:

    import { BrowserRouter } from "react-router-dom";
    
    <BrowserRouter>
      <App />
    </BrowserRouter>;
    

一切皆组件

  • 函数组件

    组件的核心作用是返回 React 元素,那么最简单的组件就是一个返回 React 元素的函数

    function Welcome(props) {
      //Welcome是一个用函数定义的组件
      return <h1>Hello, {props.name}</h1>;
    }
    
  • 类组件

    使用类(class)定义组件,返回 React 元素的工作具体就由组件的 render 方法承担

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

    使用类定义的组件,render 方法是唯一必需的方法

  • 无状态组件

    无状态组件是最基础的组件形式,纯静态展示的作用,如固定的按钮,标签等,它的基本组成结构就是属性(props),在有一个渲染函数(render),不涉及到状态的更新,所以无状态组件的复用性最强

  • 有状态组件

    有状态组是在无状态组件的基础之上,如果内部包含 state 并且外部数据发生变化时会随之发生变化,此时就是有状态组件。有状态组件是有生命周期的,用来在不同的时刻触发状态(state)的更新

    我们通常使用 props 和 state 来处理数据。无状态组件(也称为哑组件)使用 props 来存储数据,而有状态组件(也称为智能组件)使用 state 来存储数据。

  • 高阶组件

    高阶组件可以看作 React 对装饰模式的一种实现,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
    

    高阶组件( HOC)是 React 中的高级技术,用来重用组件逻辑。但高阶组件本身并不是 ReactAPI。它只是一种模式,这种模式是由 React 自身的组合性质必然产生的。

    function visible(WrappedComponent) {
      return class extends Component {
        render() {
          const { visible, ...props } = this.props;
          if (visible === false) return null;
          return <WrappedComponent {...props} />;
        }
      };
    }
    

    上面的代码就是一个 HOC 的简单应用,函数接收一个组件作为参数,并返回一个新组件,新组建可以接收一个 visible props,根据 visible 的值来判断是否渲染 Visible。

    可以通过以下两种方式实现高阶组件: 属性代理 + 反向继承

React 组件通信

  • 父组件向子组件通讯

    父组件可以向子组件通过传 props 的方式,向子组件进行通讯;
    父组件将需要传递的参数通过 key={xxx}方式传递至子组件,子组件通过 this.props.key 获取参数.

    import React from 'react'
    import Son from './son'
    class Father extends React.Component {
      constructor(props) {
        super(props)
      }
      state = {
        info: '父组件',
      }
      handleChange = (e) => {
        this.setState({
          info: e.target.value,
        })
      }
      render() {
        return (
          <div>
            <input type='text' value={this.state.info} onChange={this.handleChange} />
    
            <Son info={this.state.info} />
          </div>
        )
      }
    }
    export default Father
    
    // 子组件
    import React from 'react'
    interface Props {
      info?: string
    }
    class Son extends React.Component<IProps> {
      constructor(props) {
        super(props)
      }
      render() {
        return (
          <div>
            <p>{this.props.info}</p>
          </div>
        )
      }
    }
    export default Son
    
    
  • 子组件向父组件通讯

    props + callback 的方式,父组件向子组件传递 props 进行通讯,此 props 为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中

    // 父组件
    import React from 'react'
    import Son from './son'
    class Father extends React.Component {
      constructor(props) {
        super(props)
      }
      state = {
        info: '',
      }
      callback = (value) => {
        // 此处的value便是子组件带回
        this.setState({
          info: value,
        })
      }
      render() {
        return (
          <div>
            <p>{this.state.info}</p>
            <Son callback={this.callback} />
          </div>
        )
      }
    }
    export default Father
    
    // 子组件
    import React from 'react'
    interface Props {
      callback: (string) => void
    }
    class Son extends React.Component<IProps> {
      constructor(props) {
        super(props)
        this.handleChange = this.handleChange.bind(this)
      }
      handleChange = (e) => {
        // 在此处将参数带回
        this.props.callback(e.target.value)
      }
      render() {
        return (
          <div>
            <input type='text' onChange={this.handleChange} />
          </div>
        )
      }
    }
    export default Son
    
    
  • 兄弟组件通信

    找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信

  • 跨层级通信

    Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过 Context 通信再适合不过

  • 发布订阅模式

    发布者发布事件,订阅者监听事件并做出反应,我们可以通过引入 event 模块进行通信

  • 全局状态管理工具

    借助 Redux 或者 Mobx 等全局状态管理工具进行通信,这种工具会维护一个全局状态中心 Store,并根据不同的事件产生新的状态

React Hooks

  • 示例
    一个简单的有状态组件

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

    使用 hooks 后的版本

    import { useState } from "react";
    
    function Example() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
      );
    }
    

    Example 变成了一个函数,但这个函数却有自己的状态(count),同时它还可以更新自己的状态(setCount)。这个函数之所以这么了不得,就是因为它注入了一个 hook--useState,就是这个 hook 让我们的函数变成了一个有状态的函数。

    除了 useState 这个 hook 外,还有很多别的 hook,比如 useEffect 提供了类似于 componentDidMount 等生命周期钩子的功能,useContext 提供了上下文(context)的功能等等

  • State Hooks (useState)

    import { useState } from "react";
    
    function Example() {
      const [count, setCount] = useState(0);
    }
    
    useState 是 react 自带的一个 hook 函数,它的作用就是用来声明状态变量。useState 这个函数接收的参数是我们的状态初始值(initial state),它返回了一个数组,这个数组的第[0]项是当前当前的状态值,第[1]项是可以改变状态值的方法函数。
    这种表达形式,是借用了 es6 的数组解构
    
  • Effect Hooks (useEffect)

    不使用 Effect Hooks 的写法

    class Example extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0,
        };
      }
    
      componentDidMount() {
        document.title = `You clicked ${this.state.count} times`;
      }
    
      componentDidUpdate() {
        document.title = `You clicked ${this.state.count} times`;
      }
    
      render() {
        return (
          <div>
            <p>You clicked {this.state.count} times</p>
            <button
              onClick={() => this.setState({ count: this.state.count + 1 })}
            >
              Click me
            </button>
          </div>
        );
      }
    }
    

    使用 useEffect 的写法

    import { useState, useEffect } from "react";
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // 类似于componentDidMount 和 componentDidUpdate:
      useEffect(() => {
        // 更新文档的标题
        document.title = `You clicked ${count} times`;
      });
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
      );
    }
    

    之前都把这些副作用的函数写在生命周期函数钩子里, 如 componentDidMount,componentDidUpdate 和 componentWillUnmount。而现在的 useEffect 就相当与这些声明周期函数钩子的集合体。它以一抵三; hooks 可以反复多次使用,相互独立。所以我们合理的做法是,给每一个副作用一个单独的 useEffect 钩子。这样一来,这些副作用不再一股脑堆在生命周期钩子里,代码变得更加清晰

    useEffect(() => {
      document.title = `You clicked ${count} times`;
    }, [count]); // 只有当count的值发生变化时,才会重新执行`document.title`这一句
    

    当我们第二个参数传一个空数组[]时,其实就相当于只在首次渲染的时候执行。也就是 componentDidMount 加 componentWillUnmount 的模式。不过这种用法可能带来 bug,少用

  • Context Hooks (useContext)

    • 使用场景

      考虑这样一种场景,如果组件树结构如下,现在想从根节点传递一个 userName 的属性到叶子节点 A D F,通过 props 的方式传递,会不可避免的传递通过 B C E,即使这些组件也没有使用这个 userName 属性。
      


      如果这样的嵌套树形结构有5层或10层,那么将是灾难式的开发维护体验。如果能不经过中间的节点直接到达需要的地方就可以避免这种问题,这时 Context api 就是来解决这个问题的。
      
      Context api 是在组件树中传递数据但不用每层都经过的一种 api。下面我们一起看看 Context Hook 的使用方法。
      
    • 使用步骤

      接下来我们来研究如何使用 Context 将 username 从 App 传递到 ComponentF,共分为以下 3 个步骤

      • 创建 context

        在根节点 App.tsx 中使用 createContext() 来创建一个 context

        const UserContext = React.createContext("");
        

        创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

      • 提供 Provider

        在根节点中使用 Provider 包裹子节点,将 context 提供给子节点

        <UserContext.Provider value={'chuanshi'}>
          <ComponentC />
        </UserContext.Provider>
        

        每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
        Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
        当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
        通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。

        // App.js 完整代码
        import React from "react";
        import "./App.css";
        import ComponentC from "./components/16ComponentC";
        
        export const UserContext = React.createContext("");
        
        const App = () => {
          return (
            <div className="App">
              <UserContext.Provider value={"chuanshi"}>
                <ComponentC />
              </UserContext.Provider>
            </div>
          );
        };
        
        export default App;
        
      • 消费 Context

        import { UserContext } from "../App"; // import context 对象
        
        <!--  使用 Consumer 进行消费 -->
        <UserContext.Consumer>
          {(user) =>
          <div>User context value {user}</div>
          }
        </UserContext.Consumer>
        

        这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。

    • useContext

      在上述的 demo 中的 component E 中通过 useContext 使用根节点创建的 Context。分为以下步骤

      1. 从 react 对象中 import useContext 这个 hook api
      2. import 根节点创建的 Context 对象(可以导入多个)
      3. 执行 useContext() 方法,将 Context 传入
      // ComponentE.js
      import React, { useContext } from "react";
      
      import ComponentF from "./16ComponentF";
      import { UserContext, ChannelContext } from "../App";
      
      function ComponentE() {
        const user = useContext(UserContext);
        const channel = useContext(ChannelContext);
        return (
          <div>
            <ComponentF />
            --- <br />
            {user} - {channel}
          </div>
        );
      }
      
      export default ComponentE;
      

      关键的一行代码是 const value = useContext(MyContext)

      useContext 方法接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
      当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext providercontext value 值。即使祖先使用 React.memoshouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
      可以理解为,useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
      useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。

    • 自定义 Hooks

      借助于 react 提供的基础 hook,我们通常也可以自定义 hook,react 规定我们自定义 hook 时,必须以 use 开头。我们来尝试自定义一个控制对话框的 hook:

      import { useState } from "react";
      
      type returnd = [boolean, (visible?: boolean) => void];
      
      const useVisible = (initVisible = false): returnd => {
        const [visible, setVisible] = useState(initVisible);
        function onVisible(value?: boolean) {
          const newValue = value === undefined ? !visible : value;
          setVisible(newValue);
        }
        return [visible, onVisible];
      };
      
      export default useVisible;
      

      首先我们利用 useState 声明了 visible 和 setVisible,然后我们定义了 onVisible 这个函数用来更改 visible,接着我们返回[visible, onVisible]。然后我们来看看如何使用:

      import { Button, Modal } from "antd";
      import useVisible from "../hooks/useVisible";
      
      const Home: React.FC = () => {
        const [visible, setVisible] = useVisible(false);
      
        const modalShow = (value: boolean) => {
          setVisible(value);
        };
      
        return (
          <div
            style={{
              marginTop: "5px",
              marginLeft: "400px",
              marginRight: "400px",
            }}
          >
            <Button type="primary" onClick={() => modalShow(true)}>
              Open Modal
            </Button>
            <Modal
              title="Basic Modal"
              visible={visible}
              onOk={() => modalShow(false)}
              onCancel={() => modalShow(false)}
            >
              <p>Some contents...</p>
              <p>Some contents...</p>
              <p>Some contents...</p>
            </Modal>
          </div>
        );
      };
      
      export default Home;
      
  • 虚拟 Dom

    直接操作 DOM 是非常耗费性能的,这一点毋庸置疑。但是 React 使用 VitrualDom 也是无法避免操作 DOM 的。
    VitrualDom 的优势在于 React 的 Diff 算法和批处理策略, React 在页面更新之前,提前计算好了如何进行更新和渲染 DOM

Redux

Redux 是 React的一个状态管理库,它基于flux。 Redux简化了React中的单向数据流。 Redux将状态管理完全从React中抽象出来。
  • 三大原则

    • 单一事实来源: 整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。

    • 状态是只读的: 改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。

    • 使用纯函数进行更改: 为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。

  • 涉及概念

    • Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store。
    • State:Store 对象包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。
    • Action:State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
    • Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。
    • Reducer:Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
    • dispatch:是 View 发出 Action 的唯一方法。

    工作流程

    首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法。
    
    然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
    
    State 一旦有变化,Store 就会调用监听函数,来更新 View
  • Redux 数据循环细节

    • Action

      Action 只是一个简单的 json 对象,type 和有 payload 作为键。type 是必须要有的,payload 是可选的

      // action
      {
        type:"SEND_EMAIL",
        payload: data
      };
      
    • Creator

      Action Creators:这些是创建 Actions 的函数,因此我们在派发 action 时不必在组件中手动编写每个 action

      // action creator
      
      export function sendEamil(data) {
        return { type: "SEND_EMAIL", payload: data };
      }
      
    • Reducers

      Reducers 是纯函数,它将 action 和当前 state 作为参数,计算必要的逻辑并返回一个新的 state。 这些 Reducers 没有任何副作用

      export default function emailReducer(state = [], action) {
        switch (action.type) {
          case "SEND_EMAIL":
            return Object.assign({}, state, {
              email: action.payload,
            });
          default:
            return state;
        }
      }
      
    • 组件与 redux 进行连接

      // import connect
      import { connect } from "react-redux";
      import { bindActionCreators } from "redux";
      
      // import action creators
      import * as userActions from "../../../actions/userActions";
      
      export class User extends React.Component {
        handleSubmit() {
          // dispatch an action
          this.props.actions.sendEmail(this.state.email);
        }
      }
      
      // you are mapping you state props
      const mapStateToProps = (state, ownProps) => ({ user: state.user });
      // you are binding your action creators to your props
      const mapDispatchToProps = (dispatch) => ({
        actions: bindActionCreators(userActions, dispatch),
      });
      
      export default connect(mapStateToProps, mapDispatchToProps)(User);
      
  • Redux 的优点:

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

react-redux

  • Provider: Provider 的作用是从最外部封装了整个应用,并向 connect 模块传递 store

  • connect: 负责连接 React 和 Redux

    • 获取 state: connect 通过 context 获取 Provider 中的 store,通过 store.getState()获取整个 store tree 上所有 state
    • 包装原组件: 将 state 和 action 通过 props 的方式传入到原组件内部 wrapWithConnect 返回一个 ReactComponent 对象 Connect,Connect 重新 render 外部传入的原组件 WrappedComponent,并把 connect 中传入的 mapStateToProps, mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent
    • 监听 store tree 变化: connect 缓存了 store tree 中 state 的状态,通过当前 state 状态和变更前 state 状态进行比较,从而确定是否调用 this.setState()方法触发 Connect 及其子组件的重新渲染

redux 异步中间件

  • redux-thunk
  • redux-saga
  • redux-observable

Vue 和 React 的异同

  • 相同:

    都使用了虚拟 dom、
    都有 ssr、
    都支持 jsx,
    性能好、
    响应式、
    数据驱动、
    将注意力集中保持在核心库,同时也关注路由和负责处理全局状态管理的辅助库。

  • 区别:

    vue 使用于小项目、vue 学习成本低、vue 渲染更快、

    vue 有模板, 而 react 是 jsx、

    vue 双向数据绑定, react 是单向数据流、
    vue 有指令语法(react 中没有 v-for 指令,所以循环渲染的时候需要用到 map()方法来渲染视图,)

Angular

  • 组件声明

    ng generate component header(组件名)
    

    如 app-header”组件名,引用时使用


  • 事件绑定

    使用()绑定事件 (事件)="fn"

    <button (click)="showDetail(可选)">详情</button>
    
    showDetails(可选) {
      console.log('点击了',可选)
    }
    
  • 值绑定

    使用[ ], [属性] = ‘值’

    <input type="text" [value]="inputvalue" />
    
  • 结构型指令

    星号(*ng)被放在指令的属性名之前。

    • *ngIf

      <div *ngIf="hero">{{hero.name}}</div>
      
    • *ngFor

      <div *ngFor="let item of 数组></div>
      
  • 输入框双向绑定

    • 绑定原生事件($event):

      keyup = "inputChangeHander($event)";
      
    • 接收

        inputChangeHander(e) {
      const item = e.target.value
      this.inputvalue = item
      }
      
  • 组件通讯

    • 父传子

      • 子组件

        import { Component, OnInit } from "@angular/core";
        import { Input, Output, EventEmitter } from "@angular/core";
        @Component({
          selector: "app-list-item",
          templateUrl: "./list-item.component.html",
          styleUrls: ["./list-item.component.css"],
        })
        export class ListItemComponent implements OnInit {
          // @Input() 属性名 获取父组件的值
          @Input() listitem;
          @Output() showDetail = new EventEmitter();
        
          constructor() {}
          ngOnInit(): void {}
        }
        
      • 父组件

        <div class="app">
          <app-list-item
            [listItem]="list"
            (showDetail)="showDetails($event)"
          ></app-list-item>
        </div>
        
    • 子传父

  • 父组件调用子组件属性方法

    • 模板变量

      如果父组件要访问子组件的属性或者方法,可以通过设置模板变量来实现。

      template:`
      <button (click)="child.start()"></button>
      <app-child #child></app-child>
      `
      

      父组件引用了子组件,并在子组件上声明了模板变量#child,可以通过#child 访问子组件的属性及方法。本例点击按钮,调用了子组件的 start()方法。

      模板变量有局限性,即只能通过父组件的模板来调用子组件的属性或者方法。

    • ViewChild

      当父组件的类要调用时,把子组件作为 ViewChild,注入到父组件中。

      @ViewChild(ChildComponent)
      private child:  ChildComponent;
      
      doSomthing(){
        this.child.start();
      }
      
  • 指令

  • 动态加载组件

    • ng-template

      <ng-template> 元素是动态加载组件的最佳选择,因为它不会渲染任何额外的输出。

      <ng-template ad-host> </ng-template>
      
    • 锚点指令

      首先建立一个锚点指令,xx.directive.ts 文件;
      并在构造方法里注入 ViewContainerRef,来获取容器视图的访问权,这个容器就是那些动态加入的组件的宿主;
      指令文件的装饰器中包含一个指令名称selector,需要将其包含在需要显示的<ng-template ad-host> </ng-template>

      import { Directive, ViewContainerRef } from '@angular/core';
      
      @Directive({
        selector: '[ad-host]',
      })
      export class AdDirective {
        constructor(public viewContainerRef: ViewContainerRef) { }
      }
      
      
  • onPush Angular Change Detection:变化检测策略

RXJS

RXJS操作符

TypeScript

  • 基础类型

      TS的基础类型有除了有我们所熟悉的BooleanNumberStringArraynullundefined,还有新增了enumanyvoidnever
  • Boolean 布尔类型

    布尔类型只有true(真)和false(假)两个值
    
    var male: boolean = true;
    
  • Number 数字类型

    TS 所有的数字都是由浮点数表示的,这些数字的类型就是 number
    
    var age: number = 18;
    
  • String 字符串类型

    string表示文本数据类型。 和JavaScript一样,可以使用双引号( “)或单引号(’)表示字符串。
    
    var name: string = "JF老师";
    

    还可以使用模板字符串 它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围( `),并且以${ expr }这种形式嵌入表达式

    let name: string = "JF老师";
    let greet: string = `你好呀~${name}`;
    
    console.log(greet); //你好呀~JF老师
    
  • Array 数组类型

    如果数组是一组相同数据类型的集合,那么这个数组有两种方式来指定数据类型。

    语法规则:
    数组-类型方式(又名数组泛型):Array<type>
    类型-数组方式:type[]
    
    //数组-类型方式:
    var skills: Array<string> = ["ES5", "ES6"];
    //类型-数组方式:
    var hobbies: string[] = ["前端", "电竞"];
    
    // 使用ES6的箭头函数,遍历输出skills的所有值。
    skills.forEach((x) => console.log(x));
    // ES5
    // ES6
    hobbies.forEach((x) => console.log(x));
    // 前端
    // 游泳
    

    如果数组中的元素数据类型不相同,我们可以称之为元组(Tuple)。

    语法规则:
    [typeA, typeB, … typeX]
    
    let x: [string, number, boolean];
    x = ["hello", 1, true];
    console.log(x[0]); // hello
    
    x = [1, 1, 1]; // 报错
    
  • Enum 枚举类型

    枚举类型是一组可以命名数值的集合。使用枚举类型可以为一组数值赋予友好的名字。
    

    默认情况下,从 0 开始为元素编号:

    enum color {red,green,blue};
    let c:color=color.green;
    console.log(c)  // 1
    

    也可以从 1(或者其他数)开始编号

    enum color {
      red = 1,
      green,
      blue,
    }
    let c: color = color.green;
    console.log(c); // 2
    
    enum color2 {
      red = 1,
      green = 3,
      blue = 5,
    }
    let c: color2 = color2.green;
    console.log(c); // 3
    
    enum color3 {
      red,
      green,
      blue,
    }
    let c = color3[2];
    console.log(c); // blue
    
  • Any 类型

    如果我们没有为变量指定类型的话,那么它的默认类型就是 any。在 TS 中,any 表示可以接受任何类型的数据
    
    var something;
    //这样的声明等同于
    var something: any;
    
  • Void 类型

    void类型(又称为“无”类型)表示我们不期望有类型。一般用于函数的返回值,表示没有任何返回值。
    
    function shopping(name: string): void {
      //some codes
    }
    
  • Null 和 Undefined 类型

    TS中,undefinednull两者各自有自己的类型分别叫做undefinednull
    
    let u: undefined = undefined;
    let n: null = null;
    
  • Never 类型

    never类型表示的是那么些永远不存在的值的类型。更加通俗的讲,就是那些总是会抛出异常或者根本就不会有返回值的函数。
    

    never 的特性:

    1. never 是任何类型的子类型, 并且可以赋值给任何类型.
    2. 没有类型是 never 的子类型或者可以复制给 never (除了 never 本身).
    3. 在一个没有返回值标注的函数表达式或箭头函数中, 如果函数没有 return 语句, 或者仅有表达式类型为 never 的 return 语句, 并且函数的终止点无法被执行到 (按照控制流分析), 则推导出的函数返回值类型是 never.
    4. 在一个明确指定了 never 返回值类型的函数中, 所有 return 语句 (如果有) 表达式的值必须为 never 类型, 且函数不应能执行到终止点.
    // 返回never的函数必须存在无法达到的终点(抛出异常,程序提前终止)
    function error(message: string): never {
      throw new Error(message);
    }
    
    // 推断的返回值类型为never(永远不可能执行到最后)
    function fail(direction: boolean) {
      if (direction) {
        return 1;
      } else {
        return -1;
      }
      return error("永远不应该到这里");
    }
    
    // 返回never的函数必须存在无法达到的终点(死循环,程序不会终止)
    function infiniteLoop(): never {
      while (true) {}
    }
    
  • 访问修饰符

  • public,任何地方

  • private,只能在类的内部访问

  • protected,能在类的内部访问和子类中访问

  • readonly,属性设置为只读

  • const 和 readonly 的区别

  • const 用于变量,readonly 用于属性

  • const 在运行时检查,readonly 在编译时检查

  • 使用 const 变量保存的数组,可以使用 push,pop 等方法。但是如果使用 ReadonlyArray声明的数组不能使用 push,pop 等方法。

  • never, void 的区别

  • never,never 表示永远不存在的类型。比如一个函数总是抛出错误,而没有返回值。或者一个函数内部有死循环,永远不会有返回值。函数的返回值就是 never 类型。

  • void, 没有显示的返回值的函数返回值为 void 类型。如果一个变量为 void 类型,只能赋予 undefined 或者 null。

  • 枚举和常量枚举(const 枚举)的区别

  • 枚举会被编译时会编译成一个对象,可以被当作对象使用

  • const 枚举会在 ts 编译期间被删除,避免额外的性能开销

// 普通枚举
enum Witcher {
  Ciri = "Queen",
  Geralt = "Geralt of Rivia",
}
function getGeraltMessage(arg: { [key: string]: string }): string {
  return arg.Geralt;
}
getGeraltMessage(Witcher); // Geralt of Rivia
// const枚举
const enum Witcher {
  Ciri = "Queen",
  Geralt = "Geralt of Rivia",
}
const witchers: Witcher[] = [Witcher.Ciri, Witcher.Geralt];
// 编译后
// const witchers = ['Queen', 'Geralt of Rivia'
  • 联合类型

    联合类型通常与 nullundefined 一起使用
    
    const sayHello = (name: string | undefined) => {
      /* ... */
    };
    

    这里 name 的类型是 string | undefined 意味着可以将 string 或 undefined 的值传递给 sayHello 函数。

  • 类型别名

    type Message = string | string[]; // 给一个类型起个新名字
    
    let greet = (message: Message) => {
      // ...
    };
    
  • 可选 | 只读属性

    interface Person {
      readonly name: string; // 只读
      age?: number; // 可选
    }
    
  • interface

    // 函数类型
    interface SearchFunc {
      (source: string, subString: string): boolean;
    }
    let mySearch: SearchFunc;
    mySearch = function (source: string, subString: string) {
      let result = source.search(subString);
      return result > -1;
    };
    
    // Array
    interface StringArray {
      [index: number]: string;
    }
    
    let myArray: StringArray;
    myArray = ["Bob", "Fred"];
    
    // Class, constructor存在于类的静态部分,所以不会检查
    interface ClockInterface {
      currentTime: Date;
      setTime(d: Date);
    }
    
    class Clock implements ClockInterface {
      currentTime: Date;
      setTime(d: Date) {
        this.currentTime = d;
      }
      constructor(h: number, m: number) {}
    }
    
  • ?.、??、!.、_、** 等符号的含义

    • ?. 可选链

    • ?? ?? 类似与短路或,??避免了一些意外情况 0,NaN 以及"",false 被视为 false 值。只有 undefind,null 被视为 false 值。

    • !. 在变量名后添加!,可以断言排除 undefined 和 null 类型

    • _ , 声明该函数将被传递一个参数,但您并不关心它

    • ** 求幂

    • !:,待会分配这个变量,ts 不要担心

    // ??
    let x = foo ?? bar();
    // 等价于
    let x = foo !== null && foo !== undefined ? foo : bar();
    
    // !.
    let a: string | null | undefined;
    a.length; // error
    a!.length; // ok
    
  • const 断言

      const 断言,typescript 会为变量添加一个自身的字面量类型
    
    1. 对象字面量的属性,获得 readonly 的属性,成为只读属性
    2. 数组字面量成为 readonly tuple 只读元组
    3. 字面量类型不能被扩展(比如从 hello 类型到 string 类型)
    // type '"hello"'
    let x = "hello" as const;
    // type 'readonly [10, 20]'
    let y = [10, 20] as const;
    // type '{ readonly text: "hello" }'
    let z = { text: "hello" } as const;
    

Unit test

  • 测试用例

    先来个简单的例子,判断一个元素的文本内容,以及触发点击事件后的处理。

  • 判断文本内容

    1. 创建一个 test.vue 文件,代码如下,现在我们要测试<p class="msg">{{ msg }}</p>的内容,和触发 addCount 事件后<h1 class="number">{{ count }}</h1>的文本内容

      <template>
        <div>
          <p class="msg">{{ msg }}</p>
          <h1 class="number">{{ count }}</h1>
          <button @click="addCount">ADD</button>
        </div>
      </template>
      
      <script lang="ts">
      import { ref } from "vue";
      interface DataProps {}
      export default {
        name: "Test",
        props: {
          msg: {
            type: String,
            default: "",
          },
        },
        setup() {
          const count = ref<number>(0);
          const addCount = () => {
            count.value++;
          };
          return {
            count,
            addCount,
          };
        },
      };
      </script>
      
    2. 接下来写测试用例,在 tests/unit 下创建 test.spec.ts 文件

      import Test from "@/components/test.vue";
      import { mount, shallowMount } from "@vue/test-utils";
      describe("test.vue", () => {
        it("renders props.msg when passed", () => {
          const msg = "new msg";
          // 获取到组件实例
          const wrapper = mount(Test, {
            // 实例中的传递的props
            props: {
              msg,
            },
          });
          // 获取组件的html文本
          console.log(wrapper.html());
          // case
          expect(wrapper.get(".msg").text()).toBe(msg);
        });
      });
      

      首先引入 Test 组件,调用@vue/test-utils 中的 mount 方法,获取到 test.vue 组件实例对象 我们希望在<p class="msg">{{ msg }}</p>的文本为 new msg 启动测试命令 npm run test:unit -- --watch,注意后面的 -- --watch 是 jest 自带的类似 webpack 的--wacth

      (Vue Test Unit 入门)[juejin.cn/post/694952…]

SSR 服务端渲染

  • 概念

    服务端渲染 简称 SSR,全称是 Server Side Render,是指一种传统的渲染方式,就是在浏览器请求页面 URL 的时候,服务端将我们需要的 HTML 文本组装好,并返回给浏览器,这个 HTML 文本被浏览器解析之后,不需要经过 JavaScript 脚本的执行,即可直接构建出希望的 DOM 树并展示到页面中;

    SSR 有两种模式,单页面和非单页面模式,第一种是后端首次渲染的单页面应用,第二种是完全使用后端路由的后端模版渲染模式。他们区别在于使用后端路由的程度。

    与之相对的是 CSR(Client Side Render),是一种目前流行的渲染方式,它依赖的是运行在客户端的 JS,用户首次发送请求只能得到小部分的指引性 HTML 代码。第二次请求将会请求更多包含 HTML 字符串的 JS 文件。

  • 为什么需要 SSR

    目前前端流行的框架大都是适用于构建 SPA(单页面应用程序),在 SPA 这个模型中,是通过动态地重写页面的部分与用户交互,而避免了过多的数据交换,响应速度自然相对更高。

    但是,SPA 应用的首屏打开速度一般都很慢,因为用户首次加载需要先下载 SPA 框架及应用程序的代码,然后再渲染页面,并且 SPA 应用不利于 SEO 优化。

    这时候,人们想着是不是可以将应用首页先加载出来,然后让首页用不到的其他 JS 文件再慢慢加载。但是由于 JS 引擎是单线程的,数据的组装过程会受到阻塞,单靠浏览器端的话不容易实现。

    SSR 重新焕发活力的契机就在于此,如果将组装数据、渲染 HTML 页面的过程放在服务端,而浏览器端只负责显示接收到的 HTML 文件,那首屏的打开速度无疑会快很多。

  • SSR 的优缺点

  • 优点

    1. 更快的响应时间

      相对于客户端渲染,服务端渲染在浏览器请求 URL 之后已经得到了一个带有数据的 HTML 文本,浏览器只需要解析 HTML,直接构建 DOM 树就可以。
      
    2. 有利于 SEO

      可以将 SEO 的关键信息直接在后台就渲染成 HTML,而保证搜索引擎的爬虫都能爬取到关键数据,然后在别人使用搜索引擎搜索相关的内容时,你的网页排行能靠得更前,这样你的流量就有越高。
      
  • 缺点

    1. 相对于仅仅需要提供静态文件的服务器,SSR 中使用的渲染程序自然会占用更多的 CPU 和内存资源。

    2. 一些常用的浏览器 API 可能无法正常使用,比如 window、docment 和 alert 等,如果使用的话需要对运行的环境加以判断。

    3. 开发调试会有一些麻烦,因为涉及了浏览器及服务器,对于 SPA 的一些组件的生命周期的管理会变得复杂。

    4. 可能会由于某些因素导致服务器端渲染的结果与浏览器端的结果不一致。

  • 概述

    SSR 提高 SPA 应用的首屏响应速度,有利于 SEO 优化。

    SSR 最适用于静态展示页面,如果页面动态数据较多时需要谨慎使用。

    是否使用 SSR、使用到什么程度都需要开发者仔细权衡。

项目优化

  • 移除生产环境的控制台打印。

    方案很多,esling+pre-commit、使用插件自动去除,插件包括 babel-plugin-transform-remove-console、uglifyjs-webpack-plugin、terser-webpack-plugin。最后选择了 terser-webpack-plugin,脚手架 vue-cli 用这个插件来开启缓存和多线程打包,无需安装额外的插件,仅需在 configureWebpack 中设置 terser 插件的 drop_console 为 true 即可。最好还是养成良好的代码习惯,在开发基本完成后去掉无用的 console,vscode 中的 turbo console 就蛮好的。
    
  • 第三方库的按需加载。

    echarts,官方文档里是使用配置文件指定使用的模块,另一种使用 babel-plugin-equire 实现按需加载。element-ui 使用 babel-plugin-component 实现按需引入。
    
    前后端数据交换方面,推动项目组使用蓝湖、接口文档,与后端同学协商,规范后台数据返回。
    雅虎军规提到的,避免 css 表达式、滤镜,较少 DOM 操作,优化图片、精灵图,避免图片空链接等。
    性能问题:页面加载性能、动画性能、操作性能。Performance API,记录性能数据。
    winter 重学前端 优化技术方案:
    缓存:客户端控制的强缓存策略。
    
  • 降低请求成本:

    DNS 由客户端控制,隔一段时间主动请求获取域名 IP,不走系统 DNS(完全看不懂)。TCP/TLS 连接复用,服务器升级到 HTTP2,尽量合并域名。
    
  • 减少请求数:

    JS、CSS 打包到 HTML。JS 控制图片异步加载、懒加载。小型图片使用 data-uri。
    
  • 较少传输体积:

    尽量使用 SVG\gradient 代替图片。根据机型和网络状况控制图片清晰度。对低清晰度图片使用锐化来提升体验。设计上避免大型背景图。
    
  • 使用 CDN 加速

    内容分发网络,是建立再承载网基础上的虚拟分布式网络,能够将源站内容缓存到全国或全球的节点服务器上。用户就近获取内容,提高了资源的访问速度,分担源站压力。
    

防抖、节流

  • 节流:

    事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
    
  • 防抖:

    多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!
    
  • 使用场景:

  • 节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……

  • 防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染

    /**
     * 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。
     * @param fn要被节流的函数
     * @param delay规定的时间
     */
    function throttle(fn, delay) {
      //记录上一次函数触发的时间
      var lastTime = 0;
      return function () {
        //记录当前函数触发的时间
        var nowTime = Date.now();
        if (nowTime - lastTime > delay) {
          //修正this指向问题
          fn.call(this);
          //同步执行结束时间
          lastTime = nowTime;
        }
      };
    }
    
    document.onscroll = throttle(function () {
      console.log("scllor事件被触发了" + Date.now());
    }, 200);
    
    /**
     * 防抖函数  一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
     * @param fn要被节流的函数
     * @param delay规定的时间
     */
    function debounce(fn, delay) {
      //记录上一次的延时器
      var timer = null;
      return function () {
        //清除上一次的演示器
        clearTimeout(timer);
        //重新设置新的延时器
        timer = setTimeout(function () {
          //修正this指向问题
          fn.apply(this);
        }, delay);
      };
    }
    document.getElementById("btn").onclick = debounce(function () {
      console.log("按钮被点击了" + Date.now());
    }, 1000);
    

Webpack

  • webpack 的构建流程

  • 初始化参数:解析 webpack 配置参数,合并 shell 传入和 webpack.config.js 文件配置的参数,形成最后的配置结果;

  • 开始编译:上一步得到的参数初始化 compiler 对象,注册所有配置的插件,插件 监听 webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译;

  • 确定入口:从配置的 entry 入口,开始解析文件构建 AST 语法树,找出依赖,递归下去;

  • 编译模块:递归中根据文件类型和 loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;

  • 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据 entry 或分包配置生成代码块 chunk;

  • 输出完成:输出所有的 chunk 到文件系统;

  • webpack 常见的 loader

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)

  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)

  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试

  • image-loader:加载并且压缩图片文件

  • babel-loader:把 ES6 转换成 ES5

  • ts-loader: 将 TypeScript 转换成 JavaScript

  • sass-loader:将 SCSS/SASS 代码转换成 CSS

  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性

  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS

  • eslint-loader:通过 ESLint 检查 JavaScript 代码

  • tslint-loader:通过 TSLint 检查 TypeScript 代码

  • vue-loader:加载 Vue.js 单文件组件

  • i18n-loader: 国际化

  • cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里

  • webpack 常见的 plugin

  • ProvidePlugin:自动加载模块,代替 require 和 import,导入到全局

    //这些变量不必再import了
    new webpack.ProvidePlugin({
      jQuery: "jquery",
      React: "react",
      ReactDOM: "react-dom",
      Component: ["react", "Component"], // 导出react模块中的Component
    });
    
  • html-webpack-plugin:可以根据模板自动生成 html 代码,并自动引用 css 和 js 文件

  • extract-text-webpack-plugin:将 js 文件中引用的样式单独抽离成 css 文件

  • define-plugin: 编译时配置全局变量,如环境变量。

  • HotModuleReplacementPlugin: 热更新

  • optimize-css-assets-webpack-plugin: 不同组件中重复的 css 可以快速去重

  • webpack-bundle-analyzer: 一个 webpack 的 bundle 文件分析工具,将 bundle 文件以可交互缩放的 treemap 的形式展示。

  • compression-webpack-plugin: 生产环境可采用 gzip 压缩 JS 和 CSS

  • happypack:通过多进程模型,来加速代码构建

  • clean-webpack-plugin: 清理每次打包下没有使用的文件和 output 文件夹

  • speed-measure-webpack-plugin: 可以看至 U 每个 Loader 和 Plugin 执行耗时(整个扌丁包耗时、每个 Plugin 和 Loader 耗时)

  • webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积(业务组件、依赖第三方模块

  • imagemin-webpack-plugin: 批量压缩图片

  • serviceworker-webpack-plugin:为网页应用增加离线缓存功能

  • loader 和 plugin 的区别

  • Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

  • Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

  • Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。

  • Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。

  • source map 是什么

    sourceMap 是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码并没有阅读性可言,一旦在开发中报错或者遇到问题,直接在混淆代码中 debug 问题会带来非常糟糕的体验,sourceMap 可以帮助我们快速定位到源代码的位置,提高我们的开发效率。sourceMap 其实并不是 Webpack 特有的功能,而是 Webpack 支持 sourceMap,像 JQuery 也支持 souceMap。 既然是一种源码的映射,那必然就需要有一份映射的文件,来标记混淆代码里对应的源码的位置,通常这份映射文件以.map 结尾
    
  • webpack 热更新原理

    其实是自己开启了 express 应用,添加了对 webpack 编译的监听,添加了和浏览器的 websocket 长连接,当文件变化触发 webpack 进行编译并完成后,会通过 sokcet 消息告诉浏览器准备刷新。而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块,webpack-dev-server 可以支持热更新,通过生成 文件的 hash 值来比对需要更新的模块,浏览器再进行热替换
    
  • webpack 前端性能优化

    ⽤ webpack 优化前端性能是指优化 webpack 的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。
    
  • 使用高版本的 Webpack (使用 webpack4)

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤ webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS ⽂件, 利⽤ cssnano (css-loader?minimize)来压缩 css

  • 利⽤ CDN 加速: 在构建过程中,将引⽤的静态资源路径修改为 CDN 上对应的路径。可以利⽤ webpack 对于 output 参数和各 loader 的 publicPath 参数来修改资源路径 Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动 webpack 时追加参数 --optimize-minimize 来实现

  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存

  • 提取公共第三⽅库: SplitChunksPlugin 插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

  • treeShaking(摇树优化)机制的原理

  • treeShaking 也叫摇树优化,是一种通过移除多于代码,来优化打包体积的,生产环境默认开启。

  • 可以在代码不运行的状态下,分析出不需要的代码;

  • 利用 es6 模块的规范

  • ES6 Module 引入进行静态分析,故而编译的时候正确判断到底加载了那些模块

  • 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码

  • 是否写过 loader 和 plugin

  • Loader 支持链式调用,所以开发上需要严格遵循“单一职责”,每个 Loader 只负责自己需要负责的事情。

  • webpack 在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在特定的阶段钩入想要添加的自定义功能。Webpack 的 Tapable 事件流机制保证了插件的有序性,使得整个系统扩展性良好。

Docker

  • docker 分几个概念:镜像、容器、仓库

  • 镜像:

    就是像是我们装机时候需要的系统盘或者系统镜像文件,这里它负责创建 docker 容器的,有很多官方现成的镜像:node、mysql、monogo、nginx 可以从远程仓库下载
    
  • 容器:

    可以比拟成一个迷你的系统,例如一个只安装 mysql5.7 的 linux 最小系统,当然你喜欢也可以把 mysql、node 安装在同一个容器中,记住**,容器与容器,容器和主机都是互相隔离的**
    
  • 仓库:

        仓库就像是 github 那样的,我们可以制作镜像然后 push 提交到云端的仓库,也可以从仓库 pull 下载镜像
    

    因为虚拟机启动慢、损耗大、占用资源多,在不同系统上的迁移/拓展比较复杂; 但是 docker 它不会啊,它启动是秒级的,占用资源少,香不香,跨平台还方便复制 写好配置后,它一个命令就在电脑上同时安装不同版本的 nodej、msyql、nginx 等而且是互相隔离独立同时运行,这时候还不需要你手动来回切换

CI/CD

DevOps

瀑布开发

敏捷开发

Devops

xss

内存泄漏

Git 命令

  • git init

    在当前目录新建一个 Git 代码库
    
  • git clone [url]

    克隆项目
    
  • git status

    显示有变更的文件
    
  • git add .

    添加当前目录的所有文件到暂存区
    
  • git diff

    显示暂存区和工作区的差异
    
  • git commit -m [message]

    编辑提交信息,提交暂存区到仓库区
    
  • git branch

  • git branch

    列出所有本地分支
    
  • git branch -r

    列出所有远程分支
    
  • git branch -a

    列出所有本地分支和远程分支
    
  • git checkout -b [branch]

    建一个分支
    
  • git push origin --delete

    删除远程分支
    
  • git pull [remote] [branch]

    取回远程仓库的变化,并与本地分支合并
    
  • git push [remote] [branch]

    上传本地指定分支到远程仓库
    
  • git push [remote] --force

    强行推送当前分支到远程仓库,即使有冲突
    
  • git checkout [file]

    恢复暂存区的指定文件到工作区
    
  • git reset --hard

    重置暂存区与工作区,与上一次commit保持一致
    
  • git stash

    备份当前工作区的内容
    
  • git stash list

    显示Git栈内的所有备份
    
  • git stash pop

    从Git栈中读取最近一次保存的内容,恢复工作区的相关内容
    

单点登录 SSO(Single Sign On)

  • 概念

    指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分
    
  • 前置介绍

  1. 同源策略, 限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互,要求协议,端口和主机都相同。

  2. HTTP 用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是无状态协议,所以服务器单从网络连接上无从知道客户身份。 那要如何才能识别客户端呢?给每个客户端颁发一个通行证,每次访问时都要求带上通行证,这样服务器就可以根据通行证识别客户了。最常见的方案就是 Cookie。

  3. Cookie 是客户端保存用户信息的一种机制,保存在客户机硬盘上。可以由服务器响应报文 Set-Cookie 的首部字段信息或者客户端 document.cookie 来设置,并随着每次请求发送到服务器。子域名可以获取父级域名 Cookie。

  4. Session 其实是一个抽象概念,用于跟踪会话,识别多次 HTTP 请求来自同一个客户端。Cookie 只是通用性较好的一种实现方案,通常是设置一个名为 SessionID(名称可自定义,便于描述,本文均使用此名称)的 Cookie,每次请求时携带该 Cookie,后台服务即可依赖此 SessionID 值识别客户端。

  • 单系统登录

    浏览器第一次请求服务器需要输入用户名与密码验证身份,服务器拿到用户名密码去数据库比对,正确的话说明当前持有这个会话的用户是合法用户,应该将这个会话标记为“已授权”或者“已登录”等等之类的状态,依赖于登录后设置的 Cookie,之后每次访问时都会携带该 Cookie,从而让后台服务能识别当前登录用户
    
  • 单点登录

    相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同
    
  • 单点注销

    单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作
    

node

模块查找机制 stream 流 非阻塞异步 io 中间件

前端工程师

  • 初级前端工程师

    • 岗位职责:

      1. 负责业务系统前端模块的设计与开发;
      2. 负责产品的需求分析,开发、测试、维护等各项工作;
      3. 承担 PC 端和移动端的前端 HTML5 的开发任务;
      4. 整体页面结构及 CSS 样式层结构的设计、优化;
      5. 完成页面脚本程序编写、实现各类页面动态、交互效果;
      6. 能够理解后端架构,与后端工程师配合,为项目提供最优化的技术解决方案
    • 任职要求:

      1. 具备较强的学习欲望和能力,对前端的 JS 框架有一定的了解
      2. 熟练掌握 HTML、CSS、JS、Jquery 等
      3. 精通 DIV+CSS 页面布局,会手写样式代码,精通(X)HTML/CSS
      4. 熟练 HTML5,CSS3 等页面技术构建移动项目
      5. 熟练掌握 Vue,微信小程序,熟悉 React/Angular 相关知识
      6. 对用户体验、交互操作流程、及用户需求有一定了解
      7. 具备良好的责任心、较强的学习能力、优秀的团队沟通与协作能力
  • 中级前端工程师

    • 岗位职责:

      1. 负责所在项目需求实现设计与开发;
      2. 完成系统细节技术设计,完成核心代码的编写;
      3. 确保需求实现满足项目需求设计规范、软件编码规范以及性能要求;
      4. 准备测试案例,完成单元测试以及系统测试;
      5. 积极沟通,以确保功能实现按时、按质交付;
      6. 积极参与阶段评审,满足项目过程质量要求;审核和指导开发人员编程,确保按照系统设计执行;
    • 任职要求:

    1. 熟悉使用 html,css,javascript,熟悉 es6/es7 新特性;
    2. 要求熟练使用 Less 或者 Sass,了解 typescript,了解前端模块化规范,了解 node、npm;
    3. 熟练掌握 webpack、gulp 等构建工具,并了解底层相关原理,进行相关的性能优化
    4. 熟练使用 SVN、GIT 等代码管理工具
    5. 熟练使用 Vue、React、Angular 等相关技术栈,对原理有自己的理解;
    6. 深刻理解 Web 标准,对可用性、可访问性等相关知识有实际的了解;
    7. 对算法、数据结构、建模有一定了解;
    8. 接触过 Docker 是加分项,参加过大型开源项目是加分项。
    9. 对 Linux/Unix 操作有一定了解,会编写脚本
    10. 工作积极主动、细心,责任心强,有敬业精神,能承受一定的工作压力
  • 高级前端工程师

  • 岗位职责:

    1. 负责大型系统的 web 前端研发;
    2. 参与技术选型、推进应用和开发工作,支撑平台架构设计与开发工作;
    3. 提升系统的整体用户体验,推动前端技术的发展;
    4. 为提升团队开发效率,提炼公共组件,创造实用工具;
    5. 优化现有业务、开发流程;
    6. 关注前端发展,应用行业新技术;
    7. 团队管理;
  • 任职要求:

    1. 具有前端开发的工作经验,有大型系统的前端架构部署和实践经验;
    2. 熟悉 Vue、React、Angular 等主流 Js 框架,对它们适用的范围及优劣有独到见解,并且可以完成针对性插件开发;
    3. 精通移动端 h5 页面开发,拥有丰富的经验,对于移动端混合应用有一定的了解;
    4. 有过数据可视化开发经验,特别是大屏经验,对于 SVG、Canvas 等有深入的了解,对于业界常用的框架如 D3.js 等有一定的掌握;
    5. 有 3D 建模经验,懂 WebGL,有 threeJS 等经验尤佳;
    6. 精通至少一门非 Web 前端语言(Java、Python 等),对前后端合作模式有深入了解并有项目经验;
    7. 善于沟通,有良好的文档写作能力,口头沟通能力,良好的团队合作精神,良好的抽象思维,理性地做出技术决策,具有风险控制意识;
    8. 具有良好的软件工程意识,对数据结构和算法设计有充分理解;
    9. 有 Github 或个人技术 Blog、研究过以上工具源码者优先;
    10. 具备良好的沟通能力和团队合作精神