React(小白入门)

React Native.jpeg

前言:篇幅较长,需要耗费一些时间和精力哈

起步

npx create-react-app my-app // 1.创建项目
cd my-app // 2.打开项目
npm start // 3.启动项目
npm run eject // 4.暴露配置项

目录

├── 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

React和ReactDom

import React from 'react' 
import ReactDom from 'react-dom'

// JSX => React.createElement(...)
ReactDom.render(

Hello React

,document.querySelector('#root'))

React:负责逻辑控制,数据 -> VDOM
ReactDOM:渲染实际DOM,VDOM -> DOM
React使用JSX描述UI
babel-loader把JSX编译成相应的JS对象,React.createElement再把这个JS对象构造成React需要的虚拟dom

JSX

JSX是JavaScript的一种语法扩展,其格式比较像模板语言,但事实上完全是在JavaScript内部实现的。JSX可以很好的描述UI,能够有效提高开发效率。

  • 基本使用
// 表达式{}的使用
const name = 'Cherry'
const jsx = 
hello,{name}
React.render(jsx,document.querySelector('#root'))
  • 函数
// 函数也是合法表达式
const obj = {
  firstName: 'Harry',
  lastName: 'Potter'
}
function formatName(name) {
  return name.firstName + '-' + name.lastName
}
const jsx = 
hello ,{formatName(obj)}
  • 对象
// jsx是js对象,也是合法表达式
const greet = 
Cherry
const jsx = (
hello
{greet}
)
  • 条件语句
const show = false;
const greet = 
Cherry
const jsx = (
{show ? greet : '登录'} {show && greet}
)
  • 数组
// 数组会被当作一组元素对待,数组中存放一组jsx可用于显示列表数据
const arr = [1,2,3]
const jsx = (
  
    // diff的时候,会先比较type类型,然后是key,所以同级同类型元素,key值必须唯一 {arr.map(item => (
  • {item}
  • ))}
)
  • 属性
import logo from './logo.png';
const jsx = (
  
// 静态值用双引号,动态值用花括号;class、for等要特殊处理
)
  • 模块化
// index.js
// 命名空间 css模块化,创建index.module.css
import style from './index.module.css'
const jsx = (
  
Pink
) // index.module.css .logo { width: 100px; height: 50px; color: pink; }

组件

组件,从概念上类似于JavaScript函数。它接受任意的入参(即“props”),并返回用于描述页面展示内容的React元素。
组件有两种形式:class组件和function组件

  • class组件
    class组件通常拥有状态生命周期,继承于Component,实现render方法,下面我们用class组件来写一个小例子
// ClassComponent.js
// 导入React以及成员组件Component
import React, { Component } from 'react'
export default class ClassComponent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      date: new Date()
    }
  }
  componentDidMount() {
    // 组件挂载之后启动定时器每秒更新状态
    this.timer = setInterval(() => {
      // 使用setState来更新状态
      this.setState({
        date: new Date()
      })
    },1000)
  }
  componentWillUnmount() {
    // 组件卸载前停⽌定时器
    clearInterval(this.timer)
  }
  render() {
    return (
      
{this.state.date.toLocaleTimeString()}
) } }
  • function组件
    函数组件通常无状态,仅关注内容展示,返回渲染结果即可。

从React16.8开始引入了hooks,函数组件也能够拥有状态。

// FunctionComponent.js
import React, {useState, useEffect} from 'react'
export function FunctionComponent(props) {
  const [date,setDate] = useState(new Date())
  // 相当于 componentDidMount、componentDidUpdate、componentWillUnmount的集合
  useEffect(() => { // 副作用
    const timer = setInterval(() => { 
      setDate(new Date())
    },1000)
    return () => clearInterval(timer) // 组件卸载时执行
  },[]) 
  return (
    
{date.toLocalTimeString()}
) }

setState的使用

setState(partialState, callback)
1.partialState: object|function 用于产生与当前state合并的子集
2.callback : function state更新之后被调⽤。

import的使用(拐个弯)

import语法引用模块时,如何正确使用{}
现在有两个模块分别为A.jsB.js

  • import不使用花括号
// A.js
export default 88
// B.js 
import A from './A' // 只有在如上A.js中有默认导出的export default语法时才会生效
// 不使用{}来引用模块的情况下,import模块时的命名是随意的,如下:
import A from './A'
import sameA from './A'
import xxx from './A'
// Because 它总是会解析到A.js中默认的export default
  • import 使用花括号
// A.js
export const A = 88
// B.js
import { A } from './A' // 只有在如上模块A.js中有命名导出为A的export name的代码
import { A } from './A'  // 成功引入,因为A.js中有命名为A的export
import { sameA } from './A' // 引入失败,因为A.js中没有命名为sameA的export
import { xxx } from './A'  // 同上
// Because 明确声明了命名导出后,在另一个js中使用{}引入模块时,import时的模块命名是有意义的
// 上述代码正确执行,如下
// A.js
export const A = 88
export const sameA = 99
export const xxx = xxx

Warning: 一个模块只能有一个默认导出export default,但是可以任意命名导入很多次,也可以一次性都导入,例如:

// B.js
import A, { sameA, xxx } from './A'
// 这里我们使用导入默认导出A,以及命名导出sameA和xxx
// 我们也可以在导入的时候重命名导入
import B, {sameA as sameB, xxx as bbb} from './A'

✍ 小结一下:模块的默认导出通常用在导入整个模块的内容,而命名导出用于导入一些有用的公共方法

生命周期

生命周期方法,用于组件不同阶段执行自定义功能。在组件被创建并插入到DOM时(即挂载中阶段mounting),组件更新时,组件取消挂载或从DOM中删除时,都有可以使用的生命周期方法

  • React V16.3之前的生命周期
// LifeCyclePage.js
import React, { Component } from "react";
import PropTypes from "prop-types";

export default class LifeCyclePage extends Component {
  static defaultProps = {
    // msg: "hello",
  };
  static propTypes = {
    // msg: PropTypes.string.isRequired,
  };
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
    console.log("constructor");
  }
  componentWillMount() {
    console.log("componentWillMount");
  }
  componentDidMount() {
    console.log("componentDidMount");
  }
  shouldComponentUpdate(nextProps, nextState) {
    const { count } = nextState;
    console.log("shouldComponentUpdate", nextState);
    // return true 执行更新操作,return false 返回组件运行时
    return count !== 3;
  }
  componentWillUpdate() {
    console.log("willComponentUpdate");
  }
  componentDidUpdate() {
    console.log("componentDidUpdate");
  }
  setCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    console.log("render", this.props);
    const { count } = this.state;
    return (
      

Lifecycle

{count}

// {count % 2 && }
); } } class Child extends Component { componentWillReceiveProps(nextProps) { console.log("componentWillReceiveProps", nextProps); } componentWillUnmount() { console.log("componentWillUnmount"); } render() { console.log("Child render"); const { count } = this.props; return (

Child

{count}

); } }
  1. shouldComponentUpdate -> return true
    执行顺序: constructor => componentWillMount => render => componentDidMount => shouldComponentUpdate => willComponentUpdate => render => componentDidUpdate => over
    componentWillReceiveProps(初次渲染的时候不执行,只有在已挂载的组件接收新的props的时候,才执行)
    componentWillUnmount(组件卸载之前,常用在清除定时器等等...)
  2. shouldComponentUpdate -> return false
    执行顺序:constructor => componentWillMount => render => componentDidMount => shouldComponentUpdate => over
  • React V16.4之后的生命周期
    打开控制台的Warning,如下
    Warning.png

V17可能被废弃的三个生命周期函数用getDerivedStateFromProps替代,目前使用的话加上UNSAFE_:
componentWillMount
componentWillReceiveProps
componentWillUpdate
引入两个新的生命周期函数:
static getDerivedStateFromProps
getSnapshotBeforeUpdate

如果不想手动给将要废弃的生命周期加上UNSAFE_前缀,可以用以下命令:
npx react-codemod rename-unsafe-lifecycles 
新引入的两个生命周期函数
  • getDerivedStateFromProps

static getDerivedStateFromProps(props,state)
这个生命周期函数会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state,如果返回null则不更新任何内容。
Warning:不管原因是什么,都会在每次渲染前触发此方法。这与UNSAFE_componentWillReceiveProps形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用setState时。

  • getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps,prevState)
在render之后,在componentDidUpdate之前
这个生命周期函数在最近一次渲染输出(提交到DOM节点)之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate(prevProps,prevState,snapshot)

接下来我们看一下执行顺序
import React, { Component } from "react";
import PropTypes from "prop-types";

export default class LifeCyclePage extends Component {
  static defaultProps = {
    // msg: "hello",
  };
  static propTypes = {
    // msg: PropTypes.string.isRequired,
  };
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
    console.log("constructor");
  }
// 在render之后,在componentDidUpdate之前
 getSnapshotBeforeUpdate(prevProps, prevState, snapshot) {
    console.log("getSnapshotBeforeUpdate", prevProps, prevState, snapshot);
    // return null;
    return {
      msg: "hello,我是getSnapshotBeforeUpdate",
    };
  }
  // 在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用
  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps");
     // return null; // 不更新任何内容
    const { count } = state;
    return count > 5 ? { count: 0 } : null; // 更新state
  }
  // UNSAFE_componentWillMount() {
  //   console.log("componentWillMount");
  // }
  componentDidMount() {
    console.log("componentDidMount");
  }
  shouldComponentUpdate(nextProps, nextState) {
    const { count } = nextState;
    console.log("shouldComponentUpdate", nextState);
    // return false;
    return true;
  }
  // UNSAFE_componentWillUpdate() {
  //   console.log("willComponentUpdate");
  // }
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate", prevProps, prevState, snapshot);
  }
  setCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    console.log("render", this.props);
    const { count } = this.state;
    return (
      

Lifecycle

{count}

{/* {count % 2 && } */}
); } } class Child extends Component { // UNSAFE_componentWillReceiveProps(nextProps) { // console.log("componentWillReceiveProps", nextProps); // } componentWillUnmount() { console.log("componentWillUnmount"); } render() { console.log("Child render"); const { count } = this.props; return (

Child

{count}

); } }

组件复合

组件复合可以非常敏捷的自定义组件的外观和行为,这种方式更明确和安全。如果组件之间有公用的非UI逻辑,应该将他们抽取为js模块导入使用而不继承。日常开发中我们经常会涉及到组件复用的例子,例如头部和底部复用,下面是一个小的demo

// HomePage.js
import React, { Component } from "react";
import Layout from "./Layout";
export default class HomePage extends Component {
  render() {
    return (
      // showTopBar -> 是否展示顶部栏 
      // showBottomBar -> 是否展示底部栏
      // title -> 标题
      
        {/* 

HomePage

*/} // 具名插槽 {{ content: (

HomePage

), // 通过jsx写页面内容 txt: "这是一个文本", // 传文本 btnClick: () => console.log("call me"), // 传方法 }}
); } } // Layout.js import React, { Component } from "react"; import TopBar from "../components/TopBar"; // 顶部栏组件 import BottomBar from "../components/BottomBar"; // 底部栏组件 export default class Layout extends Component { componentDidMount() { const { title = "商城" } = this.props; document.title = title; } render() { const { children, showTopBar, showBottomBar } = this.props; console.log("children", children); return (
{showTopBar && }

{children.content}

{showBottomBar && }
{children.txt}
); } }

Redux

Redux是负责组织state的工具,提供可预测的状态管理,不仅仅是用于React。下面我们就来了解一下什么场景下使用redux,redux应该怎么使用

  • 使用场景
  1. 有着相当大量的、随着时间变化的数据;
  2. state需要有一个单一可靠数据来源;
  3. 把所有state放在最顶层组件中无法满足需求;
  4. 某个组件的状态需要共享...
  • 如何使用

安装Redux

npm install redux --save

累加器

  1. 需要一个store来存储数据;
  2. store里的reducer初始化state并定义state修改规则;
  3. 通过dispatch一个action来提交对数据的更改;
  4. action提交到reducer函数里,根据传入的action的type,返回新的state

创建store,src/store/ReduxStore.js

import { createStore } from "redux";
// 定义state初始化和修改规则,reducer是一个纯函数
function counterReducer(state = 0, action) {
  // console.log("state", state);
  switch (action.type) {
    case "ADD":
      return state + 1;
    case "MINUS":
      return state - 1;
    default:
      return state;
  }
}
const store = createStore(counterReducer);
export default store;

创建ReduxPage

import React, { Component } from "react";
import store from "../store/";

export default class ReduxPage extends Component {
  componentDidMount() {
    // 订阅状态变更
    // store.subscribe(() => {
    //   console.log("state发生变化了");
    //   this.forceUpdate(); // 强制刷新
    // });
  }
  render() {
    console.log("store", store);
    return (
      

ReduxPage

{store.getState()}

); } }

在src/index.js的render里订阅状态变更

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
ReactDOM.render(, document.getElementById("root"));

store.subscribe(() => {
  console.log("state更新了");
  ReactDOM.render(, document.getElementById("root"));
});

小结一下

  1. 通过createStore创建store
  2. reducer初始化、修改状态函数
  3. getState 获取状态值
  4. dispatch 提交更新
  5. subscribe 变更订阅

react-redux

react-redux从名字上来看就是为react量身定做的,接下来我们来了解一下如何安装和如何使用react-redux

  • 安装
npm install react-redux --save
  • 使用

react-redux提供来两个api:
1.Provider 为后代组件提供store
2. connect 为组件提供数据和变更方法

全局提供store,index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
import { Provider } from "react-redux";
ReactDOM.render(
  // 通过Provider跨层级传递把store传给后代
  
    
  ,
  document.getElementById("root")
);

获取状态数据,ReactReduxPage.js

import React, { Component } from "react";
import { connect } from "react-redux";
export default connect(
  // mapStateToProps 状态映射 把state映射到props
  (state) => ({ num: state }),
  // mapDispatchToProps 派发事件映射 把dispatch映射到props)
  {
    add: () => ({ type: "ADD" }),
  }
)(
  class ReactReduxPage extends Component {
    render() {
      const { num, dispatch, add } = this.props;
      console.log("props", this.props);
      return (
        

{num}

ReactReduxPage

{/* */} {/* 方法比较多的时候*/}
); } } );

另一种写法

class ReactReduxPage extends Component {
  render() {
    const { num, add, minus } = this.props;
    return (
      

{num}

ReactReduxPage

); } } // mapStateToProps 状态映射 把state映射到props const mapStateToProps = (state) => { return { num: state, }; }; // mapDispatchToProps 派发事件映射 把dispatch映射到props) const mapDispatchToProps = { add: () => { return { type: "ADD" }; }, minus: () => { return { type: "MINUS" }; }, }; // connect中的参数:state映射和事件映射 export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage);

react-router

react-router包含三个库,分别 react-routerreact-router-domreact-native
❄️ react-router提供最基本的路由功能,实际应用我们不会直接安装react-router,而是根据应用运行的环境选择安装react-router-dom(在浏览器中使用)或react-router-native(在rn中使用)
❄️ react-router-domreact-router-native都依赖react-router,所以在安装时,react-router也会自动安装,创建web应用

  • 安装
npm install --save react-router-dom
  • 基本使用

react-router中奉行一切皆组件的思想
路由器 -> Router
链接 -> Link
路由 -> Route
独占 -> Switch
重定向 -> Redirect
...
都是以组件形式存在

  • 举个
// RouterPage.js
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

export default class RouterPage extends Component {
  render() {
    return (
      

RouterPage

首页 用户中心 {/* 渲染的三种方式,优先级 children > component > render */}
child
} render={() =>
render
} />
); } } class HomePage extends Component { render() { return (

HomePage

); } } class UserPage extends Component { render() { return (

UserPage

); } }

Route渲染内容的三种方式:
优先级:children > component > render 这三种是互斥的关系

children: func
不管location是否匹配,你都需要渲染一些内容,这时候可以用children,除此之外和render的工作方法一样
render: func
用render的时候,调用的只是一个函数,只有在location匹配的时候渲染
component: component
只有在location匹配的时候渲染

  • 404页面

为了更好的用户体验,设定一个没有path的路由在路由列表最后面,表示一定匹配

// RouterPage.js
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";

export default class RouterPage extends Component {
  render() {
    return (
      

RouterPage

{/* 独占路由:添加Switch表示仅匹配一个 */} 首页 用户中心 {/* 渲染的三种方式,优先级 children > component > render */} {/* 根路由要添加exact,实现精确匹配 */}
child
} render={() =>
render
} />
); } } class HomePage extends Component { render() { return (

HomePage

); } } class UserPage extends Component { render() { return (

UserPage

); } } class EmptyPage extends Component { render() { return (

EmptyPage-404

); } }

PureComponent

PureComponent -> 纯组件

  • 实现性能优化(浅比较)
    缺点:必须要用class形式,而且要注意是浅比较即仅作对象的浅层⽐较,如果对象中包含复杂的数据结构,则有可能因为⽆法检查深层的差别,产⽣错误的⽐对结果。所以仅在你的props 和 state 较为简单时,才使⽤ React.PureComponent ,或者在深层数据结构发⽣变化时 调⽤ forceUpdate() 来确保组件被正确地更新
import React, { Component, PureComponent } from "react";
// PureComponent 内置了shouldComponentUpdate的比较
export default class PureComponentPage extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      // 可实现性能优化
      count: 0,
     // 实现不了,PureComponent只能实现浅比较
      obj: {
        num: 1,
      },
    };
  }
  setCount = () => {
    this.setState({
      count: 1000,
      obj: {
        num: 1000,
      },
    });
  };
  // shouldComponentUpdate(nextProps, nextState) {
  //   // 性能优化,只有值变化了才会更新
  //   return nextState.count !== this.state.count;
  // }
  render() {
    const { count } = this.state;
    console.log("render");
    return (
      

PureComponentPage

); } }
  • PureComponent于Component的比较

PureComponentComponent很相似。区别在于React.Component并未实现shouldComponentUpdate,而PureComponent中以浅层对比 propstate的方式实现了该函数。

不间断更新,未完待续...

你可能感兴趣的:(React(小白入门))