React基础入门

253 阅读5分钟

介绍

  • React 是一个用于构建用户界面的 JavaScript 库。
  • 组件逻辑使用 JavaScript 编写而非模板,所以可以轻松地在应用中传递数据,并保持状态与 DOM 分离。
  • React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。
  • 官网

开始

  • 本文基于React17.0.2版本

1. 创建项目

  • 本文使用create-react-app简称cra搭建React项目。create-react-app是FaceBook的React团队发布的一个构建React应用的脚手架工具。它本身集成了Webpack等一系列工具,执行下面代码可以快速构建一个React项目。
// 1.创建项目名称为 react17
npx create-react-app react17
// 2.进入项目
cd my-app
// 3.启动项目
npm start
// 4.暴露配置项,不执行这条命令项目目录下是没有webpack等配置文件的
npm run eject
  • cra文件结构
├── README.md ⽂档
├── public 静态资源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src 源码 
    ├── App.css
    ├── App.js 根组件
    ├── App.test.js
    ├── index.css 全局样式
    ├── index.js ⼊⼝⽂件
    └── logo.svg
├── package.json npm 依赖
  • 入口文件定义 config/webpack.config.js
  • React负责逻辑控制,数据 -> VDOM
  • ReactDom渲染实际DOM,VDOM -> DOM
  • React使⽤JSX来描述UI

2.JSX语法

const element = <h1>Hello, world!</h1>;

上面的写法就是JSX,它很像模板语言,是一个 JavaScript 的语法扩展。建议在 React 中配合使用 JSX。下面是一些使用方法:

  • 基本使用,表达式 {}
const name = "react study"; 
const jsx = <div>hello, {name}</div>;
  • 函数
const user = { fistName: "Harry", lastName: "Potter" };
function formatName(name) { 
    return name.fistName + " " + name.lastName;
}
const jsx = <div>{formatName(user)}</div>;
  • 对象
const greet = <div>good</div>;
const jsx = <div>{greet}</div>;
  • 条件语句
const show = true; //false; 
const greet = good;
const jsx = (
    <div>
        {/* 条件语句 */}
        {show ? greet : "登录"}
        {show && greet}
    </div>
);
  • 列表渲染/数组
const arr = [0, 1, 2];
const jsx3 = (
 <div>
 <ul>
 {/* key值必须唯⼀ */}
 { arr.map(item => (
         <li key={item}>{item}</li>
     )
)}
 </ul>
 </div>
);

react可以使用map或者forEach遍历数组生成节点,vue中使用的是v-for指令。

  • 属性
import logo from './logo.svg';
const jsx = (
 <div>
     <img src={logo} style={{ width: 100 }} className="img" alt="logo" />
 </div>
);

tips:静态值⽤双引号,动态值⽤花括号;因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。例如,JSX 里的 class 变成了 className

  • 事件处理 React 事件的命名采用小驼峰式(camelCase),而不是纯小写。 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
export class Click2 extends Component {
    constructor(){
        super();
        this.state = {
            num:1
        }
        this.addclick2.bind(this); // cra模式下可以不进行绑定
    }
    addclick2 = ()=>{
        this.setState({
            num:this.state.num+1
        })
    }
    render(){
        return (
            <div>
                {this.state.num}
                <button onClick={this.addclick2}>click2点击加1</button>
            </div>
        )
    }
}

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.addclick2 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。 cra搭建的React项目默认启用实验性的 public class fields 语法,cra模式下可以不进行this的绑定。

  • 模块化 react中直接引入 .css文件里面的样式会作用于当前组件和所有子组件,在vue中使用了scoped实现了样式私有化。而在react中的解决方法是使用css module。下面的示例将css文件作为一个模块引入,这个模块中的所有css,只作用于当前组件,不会影响其后代组件。

index.module.css

.pcolor {
    color: #f00;
}

App.js

import css1 from './index.module.css'
const jsx = (
 <p className={css1.pcolor}>
    字体颜色变成了红色
 </p>
);

3.组件

  • 组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
  • 两种创建组件方式 函数组件class 组件 函数组件 通常⽆状态,仅关注内容展示,返回渲染结果即可。函数组件中想要使用state或其他React特性(响应式功能等)需要用到Hook

pages/FunctionComponent.js

export default function FunctionComponent(props) {
    return <h5> 我的名字是{props.name} </h5>
}

导入并渲染函数组件

import FunctionComponent from "./pages/FunctionComponent";

const element = <FunctionComponent name="小明" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

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

pages/ClassComponent.js

import {Component} from "react";

export default class ClassComponent extends Component{
    constructor(props) {
        super(props)
        this.state = {
            number: props.num
        }
    }
    componentDidMount() {
        // 在组件挂在之后的生命周期中,将传入的num值*2
        this.setState(
            { number: this.state.number * 2}
        )
    }
    render() {
        return(
            <div>
                <h3>class组件</h3>
                <p>将传入的值*2等于{ this.state.number }</p>
            </div>
        )
    }
}

导入并渲染class组件

import ClassComponent from './pages/ClassComponent';

const element = <ClassComponent num="200" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

tips 组件名称必须以大写字母开头。React 会将以小写字母开头的组件视为原生 DOM 标签。例如,div/p/img等

  • props的只读性:组件无论是使用函数声明还是通过 class 声明都决不能修改自身的 props。所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

4.正确使用setState

直接修改state不会触发视图更新,应该使用setState()方法。

setState(partialState, callback)

  1. partialState : object | function ⽤于产⽣与当前state合并的⼦集。
  2. callback : function state更新之后被调⽤。
  • State更新可能是异步的
export default class SetStatePage extends Component{
    constructor() {
        super();
        this.state = {
            counter: 0
        }
    }

    changeValue = (v)=> {
        this.setState({
            counter: this.state.counter + v
        })
        console.log("----counter", this.state.counter); // 和上面setState同时执行 输出 0
    }

    setCounter = ()=> {
        this.changeValue(1)
    }

    render() {
        const {counter} = this.state;
        return (
            <div>
                <h3>SetStatePage</h3>
                <button onClick={this.setCounter}>{counter}</button>
            </div>
        )
    }
}

由于异步更新,上面输出的counter和setState同时执行,所以每次输出都是 函数执行this.state.counter + v 之前的数值。这有时候并不符合我们的预期,想要获取最新状态值有以下三种方式:

// 1.在回调中获取状态值
    changeValue = (v)=> {
        this.setState(
            {
                counter: this.state.counter + v
            },
            () => {
                console.log("----在回调函数中输出的counter", this.state.counter); // -> 1
            }
        )
        console.log("----没在回调函数中输出的counter", this.state.counter); // -> 0
    }
// 2.使用定时器
    setCounter = ()=> {
        setTimeout(() => {
            this.changeValue(1);
        },0)
    }
// 3.原生事件中修改状态
    // ...
    componentDidMount() {
        document.getElementById("btn").addEventListener('click', this.setCounter);
    }
    // ...
    <button id="btn">{counter}</button>

总结 setState只有在合成事件和⽣命周期函数中是异步的,在原⽣事件和setTimeout中都是同步的,这⾥的异步其实是批量更新。

  • State 的更新会被合并
    setCounter = ()=> {
        this.changeValue(1);
        this.changeValue(2);
    }

上述代码,后面的会覆盖前面的,每次只加2。将setState参数改为函数形式,每次都会先取到最新的state,就能实现先加1再加2。

    changeValue = (v)=> {
        this.setState(state => ({
            counter: state.counter + v
        })
        )
    }