介绍
- 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)
partialState: object | function ⽤于产⽣与当前state合并的⼦集。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
})
)
}