React16 全家桶学习笔记 2.0

924 阅读3分钟

写在最前面

温故而知新,勤能补拙,一遍学不会就多学几遍。

这次学习的是 【智能社】React合集(经典课程升级版+精通Hook) 课程。

ke.qq.com/course/pack…

边学习边记录

快速访问


React 基础

  • 虚拟 DOM(比直接操作 DOM 性能高)
  • JSX(依赖 React 不能单独使用)
  • 组件 Component
  • Redux 数据管理
  • React-Nactive 编写原生移动应用
  • React-Server 服务端渲染 React 组件

在网页中使用 React 和 JSX

raw.githubusercontent.com/reactjs/rea…

<div id="root"></div>

<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
    ReactDOM.render(<h1>Hello, world!</h1>, document.getElementById('root'));
</script>

使用官网脚手架创建 React 应用

  • 安装
npx create-react-app my-app
  • 运行
yarn start

yarnFacebook 推出的包管理工具,所以懂。也可以使用 npm start 运行。

优化
  • 根目录 .git 文件夹可删除

组件

  • 组件的功能应该单一和简单化
  • 好的组件不是设计出来的,是改出来的
  • 子组件间数据公用,由父组件管理数据,尽量不使用 redux
  • 组件职责,组件自己搞定自己的事情,而非父级处理
  • 推荐 return 写法
return (
    //
);
  • 花括号 {} 可传任何内容(字符串、变量、数组、循环、组件等)
className={`btn ${btn.active==='active'?'active':''}`}

使用 ES6 的 class 来定义组件

src/components/Welcome.js

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

正确绑定到组件 this 需要使用 ES6 的 bind 方法

fun(){}
...

<h1 onClick={this.fun.bind(this)}>Hello</h1>
状态 state
  • 变化就会触发 render 再渲染
  • 只能在 constructor 初始化
  • 所有变化的数据都放在状态里

设置

constructor(...args){
    super(...args);
    this.state = {
        age:18
    };
}

读取

{this.state.age}

更新

this.setState({
    a:this.state.age+1
});
属性 props
  • 组件传参
  • 只读数据不能修改

设置

<Welcome name="OY"/>

读取

<h1>Hello, {this.props.name}</h1>
渲染触发条件
  1. state 状态更新
  2. props(父组件 state 以属性的方式传递给子组件,父组件更新 state 就会触发子组件再渲染)
class Parent extends React.Component {
    constructor(...args){
        super(...args);
        this.state = {
            age:18
        };
    }

    fun(){
        this.setState({
            a:this.state.age+1
        });
    }

    render() {
        return (
            <>
                <h1 onClick={this.fun.bind(this)}>Hello</h1>
                <Child age={this.state.age}/>
            </>
        );
    }
}

3. 强制渲染 forceUpdate() 可以在不使用 state 的情况下强制渲染,不建议使用!

class Parent extends React.Component {
    constructor(...args){
        super(...args);
        this.age = 18;
    }

    fun(){
        this.age++;
        this.forceUpdate();
    }

    render() {
        return (
            <>
                <h1 onClick={this.fun.bind(this)}>Hello</h1>
                <Child age={this.age}/>
            </>
        );
    }
}
生命周期

WechatIMG210.jpeg

  1. 创建 Mount:
  • 构造器 constructor
  • render
  • componentDidMount 最常用适合获取数据
  1. 更新 Update:
  • shouldComponentUpdate 确定是否更新(重要)
  • render 3个触发条件(statepropsforceUpdate())
  • componentDidUpdate 最常用适合获取数据更新
  1. 卸载 Unmount:
  • componentWillUnmount

一套代码说清楚全部

class Demo extends React.Component {
    constructor(...args){
        super(...args);
        console.log('[创建] - 构造器');
        this.state = {
            age:18,
            show:true
        }
    }

    componentDidMount(){
        console.log('[创建] - 完成,最常用适合 fetch/axios 获取数据');
    }

    shouldComponentUpdate(nextProps, nextState){
        console.log(this.state.age, nextState.age);
        return true;
    }

    componentDidUpdate(){
        console.log('[更新] - 完成,最常用适合 fetch/axios 获取数据更新');
    }

    componentDidUpdate(){
        console.log('[卸载] - 将要,没有完成时');
    }

    add(){
        this.setState({
            age:this.state.age+1
        });
    }

    show(){
        this.setState({
            show:!this.state.show
        });
    }

    render() {
        console.log('[创建]/[更新] - 渲染');
        return (
            <>
                <h1>{this.state.age}</h1>
                <button onClick={this.add.bind(this)}>Add</button>

                {this.state.show?(<Child />):''}
                <button onClick={this.show.bind(this)}>Show</button>
            </>
        );
    }
}

函数组件 rfce

  • FC === Function Component
  • 方便(人性最爱)
  • this === undefined
  • 函数组件默认无:实例 this、状态 state、引用ref、方法
  • hook 一套工具函数的集合,函数组件专用,状态、引用、方法都可以实现

src/components/Welcome.js

function Welcome(props) {
    return (
        <h1>Hello, {props.name}</h1>
    );
}

React hook

  • React版本必须16.8+,无需特意安装 hook
  • hook 一套工具函数的集合,函数组件专用,状态、引用、方法都可以实现
  • 一定要放在 函数组件 的第一层
状态 state ush
  • useState
属性 props
  • 作为函数的参数获取 props

设置

<Welcome name="OY"/>
复制代码

读取

function Welcome(props) {
    return (
        <h1>Hello, {props.name}</h1>
    );
}

使用组件 imp

import Dialog from './components/Welcome'

ReactDOM.render((
    <>
        <Welcome name="Old Yellow"/>
    </>
), document.getElementById('root'));

遍历数据

  • 单行
render() {
    let arr = [1,2,3];
    return (
        arr.map((item,index)=><div key={index}>{item}</div>)
    );
}
  • 换行 ()
render() {
    let arr = [1,2,3];
    return (
        arr.map((item,index)=>(
            <div key={index}>{item}</div>
        ))
    );
}
  • 组合 {}
render() {
    let arr = [1,2,3];
    return (
        <>
            {arr.map((item,index)=>(
                <div key={index}>{item}</div>
            ))}
            <h1>Hello, OY!</h1>
        </>
    );
}

组件嵌套

function Parent() {
    return (
        <ul>
            <Child/>
            <Child/>
            <Child/>
        </ul>
    );
}

function Child() {
    return (
        <li>li</li>
    );
}

组件引用

  • 起名字 ref
<input ref="input1" type="text"/>
  • 引用 refs 数组
this.refs

实例

class Demo extends React.Component {
    constructor(...args){
        super(...args);
        this.state = {
            num:0
        }
    }

    add(){
        this.setState({
            num:Number(this.refs.input1.value)+Number(this.refs.input2.value)
        });
    }

    render() {
        return (
            <>
                <input ref="input1" type="text"/>
                +
                <input ref="input2" type="text"/>
                =
                {this.state.num}
                <button onClick={this.add.bind(this)}>Add</button>
            </>
        );
    }
}

组件通信

  • 父找子
  • 子找父
  • 非父子级
父找子

父组件想修改子组件的状态 state,可以给子组件定义 ref,父组件通过 ref 再引用子组件的方法来更新子组件的状态。

父组件

class Demo extends React.Component {
    constructor(...args){
        super(...args);
    }

    add(){
        // 父组件通过 `ref` 来引用子组件的 `add` 方法来更新子组件的状态。
        this.refs.child1.add(Number(this.refs.input1.value));
    }

    render() {
        return (
            <>
                <h2>Parent</h2>
                <input ref="input1" type="text"/>
                <button onClick={this.add.bind(this)}>Add</button>
                <Child ref="child1"/>
            </>
        );
    }
}

子组件

class Child extends React.Component {
    constructor(...args){
        super(...args);
        this.state = {
            num:0
        }
    }

    add(n){
        this.setState({
            num:this.state.num+n
        });
    }

    render() {
        return (
            <>
                <h2>Child</h2>
                {this.state.num}
            </>
        );
    }
}
子找父

子组件想修改父组件的状态 state,可以将父组件的 this 通过 props 属性传递给子组件,这样子组件就可以使用父组件的的方法来修改父组件的状态。

<Child parent={this}/>

父组件

class Demo extends React.Component {
    constructor(...args){
        super(...args);
        this.state = {
            num:0
        }
    }

    add(n){
        this.setState({
            num:this.state.num+n
        });
    }

    render() {
        return (
            <>
                <h2>Parent</h2>
                {this.state.num}
                <Child parent={this}/>
            </>
        );
    }
}

子组件

class Child extends React.Component {
    constructor(...args){
        super(...args);
    }

    add(){
       // 将父组件的 `this` 通过 `props` 属性传递给子组件,使用父组件的的方法来修改父组件的状态。
       this.props.parent.add(Number(this.refs.input1.value));
    }

    render() {
        return (
            <>
                <h2>Child</h2>
                <input ref="input1" type="text"/>
                <button onClick={this.add.bind(this)}>Add</button>
            </>
        );
    }
}
Redux

非父子级

路由

  • 通过不同地址,展现不同组件
  • 路由都是通过 history 实现的

react-router-dom

react-routerDOM 绑定版

安装
yarn add react-router-dom
npm install --save react-router-dom@5.2.0
三大对象
  1. Router 包裹在最外层,就像数组的 [] 包裹
  2. Route 路由配置
  3. Link 路由跳转的链接
Router

推荐放在 index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router} from 'react-router-dom';
import App from './App';

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

路由表 同样 推荐放在 App.js

import {Route, Link} from 'react-router-dom';
import Index from './components/Index';
import Work from './components/Work';

function App() {
  return (
    <>
      <Link to="/">Index</Link>
      <Link to="/work">Work</Link>

      {/* 路由表 */}
      <Route path="/" exact component={Index} />
      <Route path="/work" component={Work} />
    </>
  );
}

export default App;
Link

其实会生成一个 a 链接,需要用户点击

<Link to="/">Index</Link>
<Link to="/work">Work</Link>

带更多参数

<Link to={{
    pathname:'/',
    search:'?name=oy',
    hash:'#ag1'
}}>Index</Link>
Redirect

自动跳转,无需点击,适合判断跳转

<Redirect to="/login">Login</Redirect>
路由参数
  • 设置
<Route path="/article/:id" component={Article} />
  • 链接
<Link to="/article/1">01</Link>
<Link to="/article/2">02</Link>
...
  • 读取参数 props.match.params
function Article(props) {
    let {id} = props.match.params;
    return (
        <div>
            {id}
        </div>
    )
}
路由嵌套
  • 路由表可以写在任何组件内
  • 所谓嵌套就是在子组件内再写路由表
  • 嵌套规则 article article/1 article/1/1 article/1/1/1
  • 解耦合建议使用 props.match 里的 path 来配置,可以实现无限极嵌套

App.js

import {Route, Link} from 'react-router-dom';
import Article from './components/Article/Article';

function App() {
  return (
    <>
      {/* 路由表 */}
      <ul>
          <li><Link to="/article">Article</Link></li>
      </ul>

      <Route path="/article" component={Article} />
    </>
  );
}

export default App;

Article.js 嵌套子路由 Article_1.js

  • {${path}/1} 等于来自上级目录的 /article/ 加上 1
import React from 'react'
import {Route, Link} from 'react-router-dom';
import Article_1 from './Article_1';

function Article(props) {
    let {path} = props.match;
    return (
        <>
            <h2>Article</h2>
            {/* 路由表 */}
            <ul>
                <li><Link to={`${path}/1`}>Article_1</Link></li>
            </ul>

            <Route path={`${path}/1`} component={Article_1} />
        </>
    )
}

export default Article

Article_1.js 再嵌套子路由 Article_1_1.js

  • {${path}/1} 等于来自上级目录的 /article/1/ 加上 1
import React from 'react'
import {Route, Link} from 'react-router-dom';
import Article_1_1 from './Article_1_1';

function Article_1(props) {
    let {path} = props.match;
    return (
        <>
            <h2>Article1</h2>
            {/* 路由表 */}
            <ul>
                <li><Link to={`${path}/1`}>Article_1_1</Link></li>
            </ul>

            <Route path={`${path}/1`} component={Article_1_1} />
        </>
    )
}

export default Article_1

Article_1_1.js

import React from 'react'

function Article_1_1() {
    return (
        <>
            <h2>Article1_1</h2>
        </>
    )
}

export default Article_1_1
路由跳转
  • push 堆栈尾添加
  • pop 堆栈尾取出
  • replace 替换

通过 this.props.history 实现各种跳转

  • this.props.history.go(7) 或返回 this.props.history.go(-7)
  • this.props.history.goBack() 等于 this.props.history.go(-1)
  • this.props.history.goForward() 等于 this.props.history.go(1)
  • this.props.history.push('/article')
  • push 带参数 this.props.history.push({ pathname:'/', search:'?name=oy', hash:'#ag1' })
  • this.props.history.replace('/article') 无法回退
  • replace 带参数 this.props.history.replace({ pathname:'/', search:'?name=oy', hash:'#ag1' })

Redux

  • state 状态管理容器,管理数据
  • 单向数据流,只能去不能回来(Vue 是双向)
  • 组件之间共通数据,适合大型应用
  • 其实可以独立使用
  • 数据不需要共享,就放组件里

单向数据流

截屏2021-07-01 下午9.42.49.png

react-redux

  • reduxreact-redux 都需要安装
  • redux 是本体,react-redux 是桥梁
安装
yarn add redux react-redux
npm install --save redux react-redux
createStore
  • import { createStore } from 'redux'
  • 推荐新建 store.js 配置
  • 组合状态对象,多个 reducer 可以使用 combineReducers
  • 使用 combineReducers后,一个 action 也会让所有 reducer 都运行,但是性能不影响

多个 reducer 以及 action 都是写在 store.js 里的

import { createStore, combineReducers } from 'redux'

function person(state = {name:'oy',age:18}, action) {
    switch (action.type) {
      case 'add':
        return {
          ...state,
          age: state.age + 1,
        };
        case 'rename':
        return {
            ...state,
            name: action.value,
        };
      default:
        return state
    }
}

function person2(state = {name2:'oy2',age2:28}, action) {
    switch (action.type) {
      case 'add':
        return {
          ...state,
          age2: state.age2 + 1,
        };
      default:
        return state
    }
}

export default createStore(combineReducers({person,person2}));
Provider
  • 用于包裹整个程序,实现跨组件共享数据
  • 也是在 index.js 使用
  • import { Provider } from 'react-redux'

index.js 引入 store.js

import { Provider } from 'react-redux'
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App city="shanghai" />
  </Provider>,
  document.getElementById('root')
);
connect
  • 起链接作用,用来读取和修改数据
  • connect 参数里 state 是来自 reduxstore 数据
  • connect 参数里 props 来自父级 <App city="shanghai" />
  • 合并后 reduxstore 数据全部会进入 props
  • connect 里封装 action,用来实现修改数据
  • import { connect } from 'react-redux'
import { connect } from 'react-redux'

function App(props) {
  const add = () => {
    props.add();
  }

  return (
    <>
        {props.person.age}
        <button onClick={add}>+</button>
    </>
  );
}

export default connect((state,props)=>{
  return Object.assign({},props,state);
},{
  add(){
    return{
      type:'add'
    }
  }
})(App);

Proxy

  • 通过代理,实现跨域访问

  • 安装 yarn add http-proxy-middleware

  • 新建 src/setupProxy.js

  • 配置 const { createProxyMiddleware } = require('http-proxy-middleware');

    module.exports = function(app) {
      app.use(
        '/api',
        createProxyMiddleware({
          target: 'http://localhost:8080',
          changeOrigin: true,
        })
      );
      app.use(
        '/admin',
        createProxyMiddleware({
          target: 'http://localhost:8080',
          changeOrigin: true,
        })
      );
    };
    
  • 例:请求访问 /admin 实际访问的是 http://localhost:8080/admin

导出

  • 经常遇到导出生产环境后 css 或者 img 路径错误404报错
  • 一级和二级目录都支持

微信图片_20240403094845.png

微信图片_20240403092535.jpg

微信图片_20240403092549.jpg

  • 页面一刷新就没了必须要用 HashRouter

微信图片_20240403092545.jpg


持续更新