React(小白入门)

作者: 羊驼驼驼驼 | 来源:发表于2020-06-16 00:41 被阅读0次
    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(<h2>Hello React</h2>,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 = <div>hello,{name}</div>
    React.render(jsx,document.querySelector('#root'))
    
    • 函数
    // 函数也是合法表达式
    const obj = {
      firstName: 'Harry',
      lastName: 'Potter'
    }
    function formatName(name) {
      return name.firstName + '-' + name.lastName
    }
    const jsx = <div>hello ,{formatName(obj)}</div>
    
    • 对象
    // jsx是js对象,也是合法表达式
    const greet = <div>Cherry</div>
    const jsx = (
      <div>
        <div>hello</div>
        {greet}
      </div>
    )
    
    • 条件语句
    const show = false;
    const greet = <div>Cherry</div>
    const jsx = (
     <div>
      {show ? greet : '登录'}
      {show && greet}
    </div>
    )
    
    • 数组
    // 数组会被当作一组元素对待,数组中存放一组jsx可用于显示列表数据
    const arr = [1,2,3]
    const jsx = (
      <ul>
        // diff的时候,会先比较type类型,然后是key,所以同级同类型元素,key值必须唯一
        {arr.map(item => (<li key={item}>{item}</li>))}
      </ul>
    )
    
    • 属性
    import logo from './logo.png';
    const jsx = (
      <div>
        // 静态值用双引号,动态值用花括号;class、for等要特殊处理
        <img src={logo} className="logo" style={{width:100,height:100}} />
      </div>
    )
    
    • 模块化
    // index.js
    // 命名空间 css模块化,创建index.module.css
    import style from './index.module.css'
    const jsx = (
      <div className=style.logo>Pink</div>
    )
    // 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 (
          <div>{this.state.date.toLocaleTimeString()}</div>
        )
      }
    }
    
    
    • 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 (
        <div>
          {date.toLocalTimeString()}
        </div>
      )
    }
    

    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 (
          <div>
            <h3>Lifecycle</h3>
            <h4>{count}</h4>
            <button onClick={this.setCount}>改变count</button>
            // {count % 2 && <Child count={count} />}
            <Child count={count} />
          </div>
        );
      }
    }
    
    class Child extends Component {
      componentWillReceiveProps(nextProps) {
        console.log("componentWillReceiveProps", nextProps);
      }
      componentWillUnmount() {
        console.log("componentWillUnmount");
      }
      render() {
        console.log("Child render");
        const { count } = this.props;
        return (
          <div>
            <h3>Child</h3>
            <p>{count}</p>
          </div>
        );
      }
    }
    
    
    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 <path>
    
    新引入的两个生命周期函数
    • 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 (
          <div>
            <h3>Lifecycle</h3>
            <h4>{count}</h4>
            <button onClick={this.setCount}>改变count</button>
            {/* {count % 2 && <Child count={count} />} */}
            <Child count={count} />
          </div>
        );
      }
    }
    
    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 (
          <div>
            <h3>Child</h3>
            <p>{count}</p>
          </div>
        );
      }
    }
    
    

    组件复合

    组件复合可以非常敏捷的自定义组件的外观和行为,这种方式更明确和安全。如果组件之间有公用的非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 -> 标题
          <Layout showTopBar={false} showBottomBar={true} title={"我的商城"}>
            {/* <div>
              <h3>HomePage</h3>
            </div> */}
            // 具名插槽
            {{
              content: (
                <div>
                  <h3>HomePage</h3>
                </div>
              ), // 通过jsx写页面内容
              txt: "这是一个文本", // 传文本
              btnClick: () => console.log("call me"), // 传方法
            }}
          </Layout>
        );
      }
    }
    // 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 (
          <div>
            {showTopBar && <TopBar />}
            <h3>{children.content}</h3>
            {showBottomBar && <BottomBar />}
            <h5>{children.txt}</h5>
            <button onClick={children.btnClick}>点我</button>
          </div>
        );
      }
    }
    

    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 (
          <div>
            <h3>ReduxPage</h3>
            <p>{store.getState()}</p>
            <button
              onClick={() => {
                store.dispatch({ type: "ADD" });
              }}
            >
              add
            </button>
          </div>
        );
      }
    }
    

    在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(<App />, document.getElementById("root"));
    
    store.subscribe(() => {
      console.log("state更新了");
      ReactDOM.render(<App />, 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传给后代
      <Provider store={store}>
        <App />
      </Provider>,
      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 (
            <div>
              <h3>{num}</h3>
              <h3>ReactReduxPage</h3>
              {/* <button onClick={() => dispatch({ type: "ADD" })}>add</button> */}
              {/* 方法比较多的时候*/}
              <button onClick={add}>add</button>
            </div>
          );
        }
      }
    );
    

    🌈 另一种写法

    class ReactReduxPage extends Component {
      render() {
        const { num, add, minus } = this.props;
        return (
          <div>
            <h3>{num}</h3>
            <h3>ReactReduxPage</h3>
            <button onClick={minus}>minus</button>
            <button onClick={add}>add</button>
          </div>
        );
      }
    }
    // 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 (
          <div>
            <h3>RouterPage</h3>
            <Router>
              <Link to="/">首页</Link>
              <Link to="/user">用户中心</Link>
              {/* 渲染的三种方式,优先级 children > component > render  */}
              <Route
                exact
                path="/"
                component={HomePage}
                children={() => <div>child</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>
        );
      }
    }
    
    

    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 (
          <div>
            <h3>RouterPage</h3>
            {/* 独占路由:添加Switch表示仅匹配一个 */}
            <Switch>
              <Router>
               <Link to="/">首页</Link>
                <Link to="/user">用户中心</Link>
                {/* 渲染的三种方式,优先级 children > component > render  */}
                 {/* 根路由要添加exact,实现精确匹配 */}
                <Route
                  exact
                  path="/"
                  component={HomePage}
                  children={() => <div>child</div>}
                  render={() => <div>render</div>}
                />
                <Route path="/user" component={UserPage} />
                <Route component={EmptyPage} />
              </Router>
            </Switch>
          </div>
        );
      }
    }
    class HomePage extends Component {
      render() {
        return (
          <div>
            <h3>HomePage</h3>
          </div>
        );
      }
    }
    class UserPage extends Component {
      render() {
        return (
          <div>
            <h3>UserPage</h3>
          </div>
        );
      }
    }
    class EmptyPage extends Component {
      render() {
        return (
          <div>
            <h3>EmptyPage-404</h3>
          </div>
        );
      }
    }
    

    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 (
          <div>
            <h3>PureComponentPage</h3>
            <button onClick={this.setCount}>{count}</button>
          </div>
        );
      }
    }
    
    • PureComponent于Component的比较

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

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

    相关文章

      网友评论

        本文标题:React(小白入门)

        本文链接:https://www.haomeiwen.com/subject/dxeozhtx.html