react基础学习

288 阅读8分钟

1.前言

react视频和![react官网](https://zh-hans.reactjs.org/)结合学习总结摘抄。

1.准备起航

创建项目:npx create-react-app my-app
打开项目:cd my-app
启动项目:npm start
暴露配置项:npm run eject

文件结构:
├── README.md ⽂档 
├── public 静态资源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src 源码
  ├── App.css
  ├── App.js 根组件
  ├── App.test.js 
  ├── index.css 全局样式
  ├── index.js ⼊⼝⽂件
  ├── logo.svg
  └── serviceWorker.js pwa⽀持
├── package.json npm 依赖

入口文件:webpack.config.js

// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;

entry: [
// WebpackDevServer客户端,它实现开发时热更新功能
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// 应⽤程序⼊⼝:src/index
paths.appIndexJs,
].filter(Boolean),

2.JSX

jsx是一个 JavaScript 的语法扩展,类似模板语言。JSX 是js对象,也是一个表达式。

const name = 'world'
const element = <h1>Hello, {name}!</h1>

// 函数
function formatName(user) {
  return user.firstName + ' ' + user.lastName
}
const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};
const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);
ReactDOM.render(
  element,
  document.getElementById('root')
);

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

const greet = <div>good</div>;
const jsx = <div>{greet}</div>;

// 因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;

// 条件语句
const show = true;
const greet = <div>good</div>;
const jsx = (
 <div>
 {show ? greet : "登录"}
 {show && greet}
 </div>
);

// 表示对象
1.方法一:
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
2.方法二:
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

// 数组
const a = [0, 1, 2];
const jsx = (
  <div>
    <ul>
      {/* diff时候,⾸先⽐较type,然后是key,所以同级同类型元素,key值必须得 唯⼀ */}
      {a.map(item => (
        <li key={item}>{item}</li>
       ))}
    </ul>
  </div>
);

// 属性
import logo from "./logo.svg";
const jsx = (
  <div>
    {/* 属性:静态值⽤双引号,动态值⽤花括号;class、for等要特殊处理。 */}
    <img src={logo} style={{ width: 100 }} className="img"/>
  </div>
);

// css模块化
import style from "./base.css";
<img className={style.logo} />

3.组件(组件名采用全驼峰式,比如Clock,ClockName,第一个单词大写)

1.class组件

class组件通常拥有状态和⽣命周期,继承于Component,实现render⽅法。

import ClassComponent from './ClassComponent'; // 引入路径(路径按自己定义的位置)
<ClassComponent /> // 使用


import React, { Component } from 'react'

export default class Clock extends Component {
  constructor(props) {
    super(props);
    this.state = {
      date: new Date()
    }
  }
  componentDidMount() { // 组件挂载完成后执行
    this.timer = setInterval(() => {
      this.setState({
        date: new Date()
      });
    }, 1000);
  }
  componentWillUnmount() { // 组件卸载之前执行
    clearInterval(this.timer)
  }
  componentDidUpdate() { // 组件更新(一直在执行)
    console.log('componentDidUpdate')
  }
  render() {
    const { date } = this.state
    return (
      <div>{date.toLocaleTimeString()}</div>
    )
  }
}

2.function组件

函数组件通常⽆状态(hooks就有状态了),仅关注内容展示,返回渲染结果即可。 难点:依赖项理解与使用

import { FunctionComponent } from './FunctionComponent';
<FunctionComponent />

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

export function FunctionComponent(props) {
  const [date, setDate] = useState(new Date());
  useEffect(() => { // 相当于componentDidMount和componentWillUnmount集合
    console.log('uesEffect')
    const timer = setInterval(() => {
      setDate(new Date());
    }, 1000);
    return () => clearInterval(timer) // 组件卸载的时候执行
  }, []); // 依赖项(useEffect里面改变了就重新执行,当前不需要改变就[])没有就一直执行
  // 依赖项相当于componentDidUpdate
  return (
    <div>
      {date.toLocaleTimeString()}
    </div>
  )
}

4.正确地使用setState

setState(partialState, callback)
1. partialState: object|function
  ⽤于产⽣与当前state合并的⼦集。

2. callback: function 
  state更新之后被调⽤。

1.不要直接修改state

// 错误
this.state.comment = 'hello'

// 正确
this.setState({comment: 'hello'})

构造函数是唯一可以给 this.state 赋值的地方

2.state 的更新可能是异步的

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

// 错误
this.setState({
  counter: this.state.counter + this.props.increment
});

// 正确
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

// 例子(获取的是旧值)
import React, { Component } from "react";
export default class SetStatePage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }
  changeValue = v => {
    this.setState({
      counter: this.state.counter + v
    });
    console.log("counter", this.state.counter);
  };
  setCounter = () => {
    this.changeValue(1);
    //  console.log("counter", this.state.counter);
  };
  render() {
    const { counter } = this.state;
    return (
      <div>
        <h3>SetStatePage</h3>
        <button onClick={this.setCounter}>{counter}</button>
      </div>
  );
}
// 如果要获取到最新状态值有以下⽅式
// 1. 在回调中获取状态值
changeValue = v => {
   this.setState({
     counter: this.state.counter + v
   }, () => {
     // 这里的callback函数就是state更新完成之后在执行的
     console.log("counter", this.state.counter);
   });
};

// 2.使⽤定时器
setTimeout(() => {
  this.setCounter();
}, 0);

// 3.原⽣事件中修改状态
componentDidMount() { // 挂载
  document.body.addEventListener('click', this.changeValue, false)
}

// 总结:setState只有在合成事件和⽣命周期函数中是异步的,在原⽣事件和setTimeout中都是同步
的,这⾥的异步其实是批量更新(所以导致合并)。上面是同步操作,这里同步指的是当前执行setState完后立马获取最新的值。

3.state的更新会被吞并

constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}
componentDidMount() {
  fetchPosts().then(res => {
    this.setState({
      posts: res.posts
    });
  });
  fetchComments().then(res => {
    this.setState({
      comments: res.comments
    });
  });
}

changeValue = v => {
  this.setState({
    counter: this.state.counter + v
  });
};
setCounter = () => {
  this.changeValue(1);
  this.changeValue(2); // 批量更新,导致后面的会覆盖前面的
};

// 解决就是链式(函数)更新state:
changeValue = v => {
  this.setState(state => ({ counter: state.counter + v }));
};
setCounter = () => {
  this.changeValue(1);
  this.changeValue(2);
};

5.组件复合与继承(children)

1.不具名

import React, { Component } from 'react';
import Layout from './Layout';

export default class HomePage extends Component {
  render() {
    return (
      <Layout title="首页">
        <div>
          home page
        </div>
      </Layout>
    );
  }
}

import React, { Component } from 'react';

export default class Layout extends Component {
  componentDidMount() {
    const { title } = this.props
    document.title = title
  }
  render() {
    const { children } = this.props
    console.log(children) // {$$typeof: Symbol(react.element), type: "div"......
    return (
      <div>
        {this.props.children}
      </div>
    )
  }
}

2.具名(传对象进去)

import React, { Component } from 'react';
import Layout from './Layout';

export default class HomePage extends Component {
  render() {
    return (
      <Layout title="首页">
        { // 传对象{}
          { // 语法
            content: (
              <div>
                <h3>home page</h3>
              </div>
            ),
            txt: '文本',
            btnClick: () => {
              console.log('button')
            }
          }
        }
      </Layout>
    );
  }
}

import React, { Component } from 'react';

export default class Layout extends Component {
  componentDidMount() {
    const { title } = this.props
    document.title = title
  }
  render() {
    const { children } = this.props
    console.log(children) // content: {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}......
    return (
      <div>
        {children.content}
        {children.txt}
        <button onClick={children.btnClick}>btn</button>
      </div>
    )
  }
}

6.生命周期

⽣命周期⽅法,⽤于在组件不同阶段执⾏⾃定义功能。在组件被创建并插⼊到 DOM 时(即挂载中阶段),组件更新时,组件取消挂载或从 DOM 中删除时,都有可以使⽤的⽣命周期⽅法。 作者:爱吃芋圆的小w 生命周期:

React V16.3之前的⽣命周期:

componentDidMount // 组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染。

componentWillUnmount // 在此处完成组件的卸载和数据的销毁。(定时器销毁,监听事件移除)

componentWillReceiveProps(nextProps) // 在接受父组件改变后的props需要重新渲染组件时使用,初次渲染不执行

shouldComponentUpdate(nextProps, nextState) // 主要用于性能优化(部分更新,不重新渲染)。因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断。唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新,意思是不会再执行componentWillUpdate和componentDidUpdate生命周期了。

render() // render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。

react.png

V16.4之后的⽣命周期:

V17可能会废弃的三个⽣命周期函数⽤getDerivedStateFromProps替代,⽬前使⽤的话加上
UNSAFE_:
  componentWillMount
  componentWillReceiveProps
  componentWillUpdate
引⼊两个新的⽣命周期函数:
  static getDerivedStateFromProps
  getSnapshotBeforeUpdate
如果不想⼿动给将要废弃的⽣命周期添加 UNSAFE_ 前缀,可以⽤下⾯的命令。
  npx react-codemod rename-unsafe-lifecycles <path>

新引⼊的两个⽣命周期函数
  static getDerivedStateFromProps(props, state)
  getSnapshotBeforeUpdate(prevProps, prevState)
  getDerivedStateFromProps 会在调⽤ render ⽅法之前调⽤,并且在初始挂载及后续更新时都会被
调⽤。它应返回⼀个对象来更新 state,如果返回 null 则不更新任何内容。
  getSnapshotBeforeUpdate() 在最近⼀次渲染输出(提交到 DOM 节点)之前调⽤。它使得组件能
在发⽣更改之前从 DOM 中捕获⼀些信息(例如,滚动位置)。此⽣命周期的任何返回值将作为参数(snapshot)传
递给 componentDidUpdate(prevProps, prevState, snapshot) 。

7.redux(类似vuex)

redux文档还有免费视频学习

有着相当⼤量的、随时间变化的数据;你的 state 需要有⼀个单⼀可靠数据来源;你觉得把所有 state 放在最顶层组件中已经⽆法满⾜需要了;某个组件的状态需要共享。 安装:npm install redux --save

1. createStore 创建store
  import {createStore} from 'redux'
  const func = (state = 0, action) => state
  const store = createStore(func)
  
2. reducer 初始化、修改状态函数
  function
  
3. getState 获取状态值
  store.getState()
  
4. dispatch 提交更新
  store.dispatch({})
  
5. subscribe 变更订阅(state变化更新,重新渲染)
  store.subscribe(() => {
    console.log("subscribe");
    this.forceUpdate(); // 强制更新视图渲染(类似vue的this.$forceUpdate())
    // this.setState({});
  });

8.react-redux

react-redux文档

安装:npm install react-redux --save

react-redux提供了两个api
  1. Provider 为后代组件提供store
  2. connect 为组件提供数据和变更⽅法(connect中的参数:state映射和事件映射)
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store/'
import { Provider } from 'react-redux'

ReactDom.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.querySelector('#root')
)
import React, { Component } from "react";
import { connect } from "react-redux";

class ReactReduxPage extends Component {
 render() {
   const { num, add, minus, dispatch } = this.props;
   return (
     <div>
       <h1>ReactReduxPage</h1>
       <p>{num}</p>
       <button onClick={() => dispatch({type: 'ADD'})}>add</button>
       <button onClick={add}>add</button>
       <button onClick={minus}>minus</button>
     </div>
   );
 }
}
export default connect(
  // 状态映射 mapStateToProps
  state => ({num: state}),
  { // 派发事件映射
    add: () => ({type: "add"}),
    minus: () => ({type: "minus"})
  }
)(ReactReduxPage);

9.react-router

安装:npm install react-router-dom --save

路由器-Router、链接-Link、路由-Route、独占-Switch、重
定向-Redirect都以组件形式存在。

Route渲染内容的三种⽅式
  Route渲染优先级:children>component>render。
  这三种⽅式互斥,你只能⽤⼀种。
children:func
  有时候,不管location是否匹配,你都需要渲染⼀些内容,这时候你可以⽤children。
  除了不管location是否匹配都会被渲染之外,其它⼯作⽅法与render完全⼀样。
render:func
  但是当你⽤render的时候,你调⽤的只是个函数。
  只在当location匹配的时候渲染。
component: component
  只在当location匹配的时候渲染。
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
export default class RouterPage extends Component {
  render() {
    return (
      <div>
        <h3>RouterPage</h3>
        <Router>
          <Link to="/">⾸⻚</Link>
          <Link to="/user">⽤户中⼼</Link>
          {/* 根路由要添加exact,实现精确匹配(只匹配一个) */}
          <Route
           exact
           path="/"
           component={HomePage}
           //children={() => <div>children</div>}
           //render={() => <div>render</div>}
          />
        <Route path="/user" component={UserPage} /></Router>
      </div>
    );
  }
}
class HomePage extends Component {
  render() {
    return (
      <div>
        <h3>HomePage</h3>
      </div>
    );
  }
}
class UserPage extends Component {
  render() {
    return (
      <div>
        <h3>UserPage</h3>
      </div>
    );
  }
}

404页面(设定⼀个没有path的路由在路由列表最后⾯,表示⼀定匹配)

{/* 添加Switch表示仅匹配⼀个*/}
<Switch>
  {/* 根路由要添加exact,实现精确匹配 */}
  <Route
    exact
    path="/"
    component={HomePage} />
  <Route path="/user" component={UserPage} />
  <Route component={EmptyPage} />
</Switch>

class EmptyPage extends Component {
  render() {
    return (
      <div>
        <h3>404</h3>
      </div>
    );
  }
}

1.PureComponent浅比较(只比较一个state,多个在一起无效)

import React, { Component, PureComponent } from "react";
export default class PureComponentPage extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }
  setCounter = () => {
    this.setState({
      counter: 100
    });
  };
  // PureComponent内置值不变化不再渲染
  // shouldComponentUpdate(nextProps, nextState) { // 当值没变化时不执行渲染
    // return nextState.count !== this.state.count;
  // }
  render() {
    const { counter, obj } = this.state;
    return (
      <div>
        <h1>PuerComponentPage</h1>
        <div onClick={this.setCounter}>counter: {counter}</div>
      </div>
    );
  }
}