react笔记
React 介绍
React 是什么
- Facebook 开源的一个JS库。
- 一个用于动态构建用户界面的JS库。
- 不是一个MCV框架,构建可复用web组件,侧重UI
React 的特点
- Declarative(声明式编码)
- Component-Based(组件化编码)
- Learn Once, Write Anywhere(支持客户端、服务器端渲染)
- 高效的DOM Diff算法,最小化页面重绘。虚拟(virtual)DOM,不总是直接操作DOM
- 单向数据流 React是单向的从数据到视图的渲染,非双向数据绑定
React高效的原因
- 虚拟(virtual)DOM,不总是直接操作DOM
- 高效的DOM Diff算法,最小化页面重绘(即“局部渲染”)。
虚拟DOM指的是:在真实DOM的上一层映射一层虚拟DOM。我们操作的是映射关系,而不是真实的DOM。假设页面的样式做了修改(比如新增了一个标签),此时修改的是虚拟DOM的样式,真实的DOM并未发生变化。那什么时候,真实的DOM会发生变化呢? 当我把所有的内容操作完之后,转化为真实的DOM,此时要打包统一的渲染页面,于是真实的DOM发生变化,然后渲染一次。 这样做的话,可以减少页面的渲染次数。
JSX和组件
react 使用 JSX 来替代常规的 JavaScript。
JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
如果碰见jsx或者tsx文件,就是用了jsx语法。其中tsx是typescript+jsx
我们不需要一定使用 JSX,但它有以下优点:
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 JSX 编写模板更加简单快速。
const element = <h1>Hello, world!</h1>;
我们推荐在 React 中使用 JSX 来描述用户界面。
每个return jsx的叫组件。
对于原生的react代码,每次创建元素很麻烦,所以我们提出了用jsx语法代替,此时代码中return的<div></div>就是一种jsx语法。jsx语法需要注意的点在于:
- 对于jsx中的标签内如果需要传值,主要要使用{}包裹,如果使用”“,则默认会传一个string的值,容易造成错误
- 对于传入的对象,我们需要用大括号包裹(类似
{"key": "value"}),此时第一层是用来表示这里面是js代码,第二层用来表示这个是个对象 - jsx其实就是一种语法模板,他用来翻译react中书写的代码,而用来将jsx语法翻译的工具叫做babel-loader,而babel-loader被webpack内置了,因为我们用的脚手架其实就是webpack的打包,所以默认我们就可以写jsx语法而不报错。
###
JSX 允许在大括号中嵌入任何表达式,所以我们可以在 map() 中这样使用:
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
如果想在jsx里加注释,就{}包起来,
想加console.log,也是这样包起来。 {}其实是返回了一个值,这个值作为jsx里用的。
return (
{console.log('xx')}
{console.log('234')}
)
每个组件就对应了一个html元素。html元素的属性会传递成组件的元素。
Hello world
<!DOCTYPE html>
<html lang="">
<head>
<meta />
<meta />
<meta />
<title>Document</title>
</head>
<body>
<!-- 引入React相关的js库 -->
<script type="text/javascript" src="./libs/react.js"></script>
<script type="text/javascript" src="./libs/react-dom.js"></script>
<script type="text/javascript" src="./libs/babel.min.js"></script>
<div id="myContainer"></div>
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
<script type="text/babel">
//页面中的真实容器元素
var containDiv = document.getElementById("myContainer");
//1、创建虚拟DOM对象
var vDom = <div>Hello, React!</div>; // 不是字符串, 不能加引号
//2、渲染虚拟DOM对象(将虚拟DOM对象渲染到页面元素中)
ReactDOM.render(vDom, containDiv); // 参数1:虚拟DOM对象;参数2:页面中的容器
</script>
</body>
</html>
React安装
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
自定义组件参数渲染
自定义组件两种方式,函数式组件和类组件。
以下代码实现了显示时间的时钟,react拿Date渲染。
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('example')
);
}
setInterval(tick, 1000);
除了函数外我们还可以创建一个 React.Component 的 ES6 类,该类封装了要展示的元素,需要注意的是在 render() 方法中,需要使用 this.props 替换 props:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('example')
);
}
setInterval(tick, 1000);
复合组件
function Name(props) {
return <h1>网站名称:{props.name}</h1>;
}
function Url(props) {
return <h1>网站地址:{props.url}</h1>;
}
function Nickname(props) {
return <h1>网站小名:{props.nickname}</h1>;
}
function App() {
return (
<div>
<Name name="菜鸟教程" />
<Url url="http://www.runoob.com" />
<Nickname nickname="Runoob" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('example')
);
有状态的组件(定时刷新等)
其实就是组件可以实现定时刷新等功能,不用操作DOM
定时功能
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
注意必须要用react api来更新state,如果直接this.state赋值不会重绘。state其实就是一个Object可以多层结构 自定义。
每次调用this.setState就重绘一次。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
// 挂载,注册定时任务
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
// 卸载定时任务
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('example')
);
每当 Clock 组件第一次加载到 DOM 中的时候,我们都想生成定时器,这在 React 中被称为挂载。
同样,每当 Clock 生成的这个 DOM 被移除的时候,我们也会想要清除定时器,这在 React 中被称为卸载。
代码解析:
- componentDidMount() 与 componentWillUnmount() 方法被称作生命周期钩子。
- 在组件输出到 DOM 后会执行 componentDidMount() 钩子,我们就可以在这个钩子上设置一个定时器。
- this.timerID 为定时器的 ID,我们可以在 componentWillUnmount() 钩子中卸载定时器。
数据自顶向下流动
父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。
这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。
State 和 Props 组件参数传递
State 和 Props 组件的参数传递、父组件传递给子组件
state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。
使用props
function HelloMessage(props) {
return <h1>Hello {props.name}!</h1>;
}
const element = <HelloMessage name="Runoob"/>;
ReactDOM.render(
element,
document.getElementById('example')
);
设置默认props属性
class HelloMessage extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// default props
HelloMessage.defaultProps = {
name: 'Runoob'
};
const element = <HelloMessage/>;
ReactDOM.render(
element,
document.getElementById('example')
props是只读的
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
组合使用 state 和 props
在应用中组合使用 state 和 props 。我们可以在父组件中设置 state, 并通过在子组件上使用 props 将其传递到子组件上。在 render 函数中, 我们设置 name 和 site 来获取父组件传递过来的数据。
class WebSite extends React.Component {
constructor() {
super();
this.state = {
name: "菜鸟教程",
site: "https://www.runoob.com"
}
}
render() {
return (
<div>
<Name name={this.state.name} />
<Link site={this.state.site} />
</div>
);
}
}
class Name extends React.Component {
render() {
return (
<h1>{this.props.name}</h1>
);
}
}
Props 验证
比如验证是String类型才行。
class MyTitle extends React.Component {
render() {
return (
<h1>Hello, {this.props.title}</h1>
);
}
}
MyTitle.propTypes = {
title: PropTypes.string
};
Props 验证使用 propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes 提供很多验证器 (validator) 来验证传入数据是否有效。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。
React 事件处理 按钮等逻辑
React 事件处理 按钮等逻辑
class Popper extends React.Component{
constructor(){
super();
this.state = {name:'Hello world!'};
}
// onclick执行逻辑
preventPop(name, e){ //事件对象e要放在最后
e.preventDefault();
alert(name);
}
render(){
return (
<div>
<p>hello</p>
{/* 通过 bind() 方法传递参数。 */}
<a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}>Click</a>
</div>
);
}
}
子组件如何给父组件传递消息? 参考
当你需要从子组件中更新父组件的 state 时,你需要在父组件通过创建事件句柄 (handleChange) ,并作为 prop (updateStateProp) 传递到你的子组件上。实例如下:
class Content extends React.Component {
render() {
return <div>
<button onClick = {this.props.updateStateProp}>点我</button>
<h4>{this.props.myDataProp}</h4>
</div>
}
}
class HelloMessage extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'Hello Runoob!'};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: '菜鸟教程'})
}
render() {
var value = this.state.value;
return <div>
<Content myDataProp = {value}
updateStateProp = {this.handleChange}></Content>
</div>;
}
}
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
React 条件渲染
在jsx里定义条件或者变量来渲染可以不同的结果。
变量控制:
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
三目运算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
React 列表 & Keys
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}> Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('example')
);
Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
React 组件 API
- 设置状态:setState 合并nextState和当前state,并重新渲染组件。
- setState是React事件处理函数中和请求回调函数中触发UI更新的主要方法。合并指如果json两个key,可以只操作一个而不影响另一个Key。
- 另外注意setState()并不会立即改变this.state,而是创建一个即将处理的state。setState()并不一定是同步的,为了提升性能React会批量执行state和DOM渲染。
- 替换状态:replaceState 不合并。
- 设置属性:setProps 设置属性,合并。
- 替换属性:replaceProps 替换属性
- 强制更新:forceUpdate
- forceUpdate()方法会使组件调用自身的render()方法重新渲染组件,组件的子组件也会调用自己的render()。但是,组件重新渲染时,依然会读取this.props和this.state,如果状态没有改变,那么React只会更新DOM。
- forceUpdate()方法适用于this.props和this.state之外的组件重绘(如:修改了this.state后),通过该方法通知React需要调用render()
- 一般来说,应该尽量避免使用forceUpdate(),而仅从this.props和this.state中读取状态并由React触发render()调用。
- 获取DOM节点:findDOMNode
- 返回值:DOM元素DOMElement
- 如果组件已经挂载到DOM中,该方法返回对应的本地浏览器 DOM 元素。当render返回null 或 false时,this.findDOMNode()也会返回null。从DOM 中读取值的时候,该方法很有用,如:获取表单字段的值和做一些 DOM 操作。
React 组件生命周期
- Mounting(挂载):已插入真实 DOM
- Updating(更新):正在被重新渲染
- Unmounting(卸载):已移出真实 DOM

componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。componentDidUpdate(): 在重绘后会被立即调用。componentWillUnmount(): 在组件卸载及销毁之前直接调用。
例子
- React AJAX 使用ajax请求服务端数据,挂载时开始异步请求数据,并设置回调,如果请求有结果了,就setState重绘,卸载时取消进行中的请求。
React 表单
HTML 表单元素与 React 中的其他 DOM 元素有所不同,因为表单元素生来就保留一些内部状态。
在 HTML 当中,像 <input>, <textarea>, 和 <select> 这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。
每个组件对应了一个html元素。html元素的属性会传递成组件的元素。
因此input value应该就是组件里state里的某个值,然后react 监听onchange,onchang时也同步更新state里value的值。相当于组件就变成了一个html表单元素。
例子:在实例中我们设置了输入框 input 值 value = {this.state.data}。在输入框值发生变化时我们可以更新 state。我们可以使用 onChange 事件来监听 input 的变化,并修改 state。
class HelloMessage extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'Hello Runoob!'};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
var value = this.state.value;
return <div>
<input type="text" value={value} onChange={this.handleChange} />
<h4>{value}</h4>
</div>;
}
}
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
子组件想更新父组件的值,就让父组件传回到函数给子组件。
父组件想更新子组件的值,就属性传递即可。父组件每次重绘,都会重绘子组件。
函数组件代替类组件
函数组件可以代替类组件,因为函数组件语法更加简单易懂,但是也面临以下两个问题
没有state
React v16.8.0推出Hooks API,其中的
useState可以解决这个问题注意:
useState不可以用if...else
没有生命周期
React v16.8.0推出Hooks API,其中的
useEffect可以解决这个问题默认每次渲染都会调用
Hook 是一个特殊的函数,它可以让你钩入React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。
函数组件和类组件对比
- 一般来说,类组件的性能消耗较函数式组件大,包括生成实例时使用的内存及shouldComponentUpdate不正确引起的不必要的更新。
- 类组件不能很好的压缩
- 实际开发中,类组件的生命周期常常包含一些不相关的逻辑
状态 useState
useState
state可以起别名,此时多次调用useState其实就设置了不同别名不同值。
import React, { useState, useEffect } from "react"
const App = props =>{//消除了this
const [n, setN] = React.useState(0)//数组前面是读,后面是写,叫法无所谓
const onClick = () => {
setN(n+1)
}
return {
<div>
{n},
<button onClick = {onClick}>+1</button>
</div>
}
}
以上方法比原先的setState要简便不少,但是麻烦的是如果有多个数据,需要多次useState而不能一次性传入多个值。
生命周期 useEffect
effect(副作用)是react函数组件用来替代生命周期的函数。你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
useEffect 接受一个函数作为参数,第二个参数表示什么时候调用它。 参数如下: useEffect(callback[,[]])
第一个参数接受一个callback回调函数,里面可以写执行业务代码
第二个参数可省略,它接受一个数组,可以是空数组,也包含了state数据
可以模拟componentDidMount componentDidUpdate componentWillUnmount
当不传递第二个参数时,每次render都会执行一遍callback函数,相当于包含第一遍render的
componentDidUpdate当传递第二个参数且是空数组时,只有第一次render才会执行callback,类似于
componentDidMount不管是否传递第二个参数,只要在
callback中return 一个函数,就相当于告诉react此组件挂掉之前执行什么操作,类似于componentWillUnMount可以监听指定是数据的变化,有变化就调用指定的函数。此时第二个参数是数组且不为空,比如
React.useEffect(() => { console.log("state发生变化了"); }, [state]); //这里传递了数组,里面是监控的数据,也可以多个变量
useContext
context(上下文)可以看成是扩大版的props,它可以将全局的数据通过provider接口传递value给局部的组件,让包围在provider中的局部组件可以获取到全局数据的读写接口
全局变量可以看成是全局的上下文
而上下文则是局部的全局变量,因为只有包围在provider中的局部组件才可以获取到这些全局变量的读写接口
设置一个父组件,一个子组件,通过useContext来传递state,并在子组件上设置一个按钮来改变全局state
这样子组件可以读父组件的state,他们其实就是state共享了。
父组件也可以读子组件设置的。
const { state, setState } = useContext(X);//拿到 名字为X的上下文的value,用两个变量来接收读写接口
useReducer
Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的state,Reducer 函数的形式是(state, action) => newState
useReducer用来引入 Reducer 功能。
useReducer语法
const [state, dispatch] = useReducer(reducer, initialState);
useReducer用来接收两个参数,分别是Reducer函数和初始state
useReducer 返回了一个数组,2个元素分别为 state 和 dispatch 方法。其中 state 在我们的例子中就是当前的 n 值,dispatch 方法接受一个参数,执行对应的 action。dispatch 执行后,对应的 state 会改变,组件会 rerender,来展示最新的状态。
reducer函数里面可以存放state的各种操作,它类似状态管理器(其实我觉得应该叫state数据管理器),通过dispatch函数的传递action,可以触发reducer函数内部运算操作,并返回当前state
一个dispatch例子
定义加法和减法
const reducer=(state,action)=>{
if(action.type==="add"){
return {
n:state.n+1
}
}else if(action.type==="sub"){
return {
n:state.n-1
}
}
}
其次,使用useReducer接收Reducer函数和一个初始state,并返回当前值state与dispatch函数
当触发事件时,使用dispatch传递action,让reducer计算出新的state
export default function App() {
const initialState = { n: 1 };
const [state, dispatch] = useReducer(reducer, initialState);
//使用useReducer接收reducer参数和初始state
return (
<>
<div>{state.n}</div>
<button
onClick={() => {
dispatch({ type: "add" }); // 传递action
}}
>
点击+
</button>
<button
onClick={() => {
dispatch({ type: "sub" });//传递action触发reducer函数
}}
>
点击-
</button>
</>
);
}
dispatch()是发出 Action 的唯一方法。它发出action后使reducer执行,并返回一个新的state。
配合useContext
我们可以使用useContext配合useReducer来帮助我们完成一些数据、操作的流转