# 对于React的学习
简介
React是Facebook公司的一个内部项目,最初用于自己构造Facebook网站,后来于2013年5月开源。由于拥有较高的性能,且代码逻辑非常简单,越来越多的人已开始关注和使用它。
React是一个用于构建用户界面的 JavaScript 库,具有如下特点:
a、声明式设计
react是面向数据编程,不需要直接去控制dom,你只要把数据操作好,react自己会去帮你操作dom,可以节省很多操作dom的代码。这就是声明式开发。
b、使用JSX语法
JSX 是 JavaScript 语法的扩展。React 开发大部分使用 JSX 语法(在JSX中可以将HTML于JS混写)。
c、灵活
react所控制的dom就是id为root的dom,页面上的其他dom元素你页可以使用jq等其他框架 。可以和其他库并存。
d、使用虚拟DOM、高效
虚拟DOM其实质是一个JavaScript对象,当数据和状态发生了变化,都会被自动高效的同步到虚拟DOM中,最后再将仅变化的部分同步到DOM中(不需要整个DOM树重新渲染)。
e、组件化开发
通过 React 构建组件,使得代码更加容易得到复用和维护,能够很好的应用在大项目的开发中。
f、单向数据流
对于jsx的理解
react是单向数据流,父组件传递给子组件的数据,子组件能够使用,但是不能直接通过this.props修改。 这样让数据清晰代码容易维护。
JSX其实就是在JS里面写XML(HTML),在React里面使用JSX来代替常规的JS。
我们不需要一定使用 JSX,但它有以下优点:
1.JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
2.它是类型安全的,在编译过程中就能发现错误。
3使用 JSX 编写模板更加简单快速。
1.在js里面写html
JSX语法允许我们直接在js里面写HTML代码
ReactDOM.render(
<div>HTML</div>,
document.getElementById('root')
)
但是在jsx里面推荐使用一个小括号来把HTML结构包起来
ReactDOM.render(
(
<div>HTML</div>
),
document.getElementById('root')
)
2.在html里面写js
JSX除了允许在js里面写HTML外,还可以在HTML里面写js,只是要求一定要在html里面加上一对大括号,然后在大括号里面写js。
jsx的大括号里面可以写你想要的表达式,可以可以写对应要渲染的变量之类的。最终会被作为js执行并渲染到页面上。
const person = { name:'狗蛋' };
ReactDOM.render((
<div>{ person.name }今年{ Math.floor(Math.random() * 100) }岁了</div>
),
document.getElementById('root')
)
大括号,相当于给html内部开辟了一个执行js的空间。
3.关于文件的后缀
react里面默认支持的可以写jsx语法的后缀有: .js/.jsx 使用ts的话文件名为.ts/.tsx
总结
1.在jsx里面可以html和js混写
2.在js里面写html要加小括号
3.在html里面写js要加大括号
4.js/jsx/tsx文件里面都可以写jsx语法
组件化开发
class组件
在早期的react用法里面,主要是class组件,但是因为这种方式量级比较重,以及使用起来比较繁琐,后期维护难度比较高,所以在后期的项目中已经基本不使用
定义一个class组件的基本格式如下。
import { Component } from "react";
export default class ComClass extends Component {
render() {
return <div>ComClass</div>;
}
}
函数式组件
react16.8开始,因为hook的推出,极大地提升了函数式组件的实用性,所以基本在外面的项目中,都全面转型到了 FC + Hook 的用法。
定义一个函数组件的基本格式如下:
export default function ComFun() {
return <div>ComFun</div>;
}
函数式组件只有一个函数,需要返回一个jsx结构。相比于class组件而言,非常地轻量。
react中的样式操作
1.样式操作
在react里面同样支持行内样式和外链样式。
a.外链样式
在组件里面引入对应的外部样式就行。
import React, { Component } from "react";
import "./tab.css";
export default class Tab extends Component {
render() {
return <div>Tab</div>;
}
}
b.行内样式
react里面只支持使用对象的方式声明行内样式。
export default class Tab extends Component {
render() {
return (
<div
style={{
width: "400px",
minHeight: "400px",
border: "1px solid #ccc",
margin: "0 auto",
}}
>
Tab
</div>
);
}
}
我们可以使用类名的方式控制样式。因为class属性与es6的class关键字会产生冲突,所以在react里面使用了className代替了class属性。
c.className
当然我们也可以使用类名的方式控制样式。但是要注意,因为class属性与es6的class关键字会产生冲突,所以在react里面使用了className代替了class属性。
export default class Tab extends Component {
render() {
return (
<div>
<div className="tab-out tab-b">Tab</div>
<div className={"tab-out"}>Tab</div>
<div className={["tab-out", "tab-b"].join(" ")}>Tab</div>
</div>
);
}
}
在react里面还有一个html属性也是和js的关键字冲突的。label标签有一个for属性,也是和js的for是冲突的,所以在react里面使用labelFor来代替。
列表渲染
在react里面没有类似vue的指令,所以我们必须自己手动循环生成。在react里面我们一般采用map方法对数组进行循环,因为map方法可以返回我们想要渲染的jsx结构。
const tabItems = ["首页", "新闻", "购物", "其它"];
const tabContent = [
"欢迎您,狗蛋先生",
"今日,全球首富狗蛋先生和翠花女士结为夫妇",
"没钱,买什么买",
"...",
];
export default class Tab extends Component {
render() {
return (
<div className="tab-out">
<ul className="flex">
{tabItems.map((item, key) => (
<li key={key}>{item}</li>
))}
</ul>
<div className="content">
{tabContent.map((item, key) => (
<section key={key}>{item}</section>
))}
</div>
</div>
);
}
}
在react里面,为了保证diff算法对于dom对象的重用处理,也需要给每个循环生成的结构添加一个key属性。
父组件向子组件传递数据
react里面父组件向子组件传递数据比较简单,只需要给子组件一个自定义属性传递就行。
<div className="todo">
{/* 1. 使用一个自定义属性向子组件传递数据 */}
<TodoList title="正在进行"/>
</div>
然后我们在子组件里面使用this.props进行接收
export default class TodoList extends Component {
render() {
return (
<div className="list">
<h2>{this.props.title}</h2>
</div>
);
}
}
子组件向父组件传递数
react里面子组件不能直接修改父组件里面的数据,只能通过调用父组件的方法来实现,也就是子组件向父组件传递数据的过程。
export default class Todo extends Component {
// 1. 在父组件里面准备一个用于操作数据的方法
addSome(value) {
console.log(value);
}
render() {
return (
<div className="todo">
{/* 2. 在子组件上直接传递操作数据的方法 */}
<TodoHeader add={this.addSome.bind(this)} />
</div>
);
}
}
export default class TodoHeader extends Component {
onButtonClick() {
// 3. 在子组件里面通过this.props获取并调用父组操作改数据的方法
this.props.add(this.state.value);
}
render() {
return (
<div className="flex todo-header">
<button onClick={this.onButtonClick.bind(this)}>添加</button>
</div>
);
}
}
多级组件传递数据
在react里面,多级组件之间传递使用得不多,但是我们还是需要掌握。
在react里面我们使用context的方式进行多级传递。其具体使用过程如下:
1.首先调用createContext方法创建一个“全局”context对象。
import { createContext } from "react"
// 声明一个defaultValue用来声明context的数据结构
export const defaultValue = { count:0 , add(val){} }
// 创建context对象
export const TodoContext = createContext(defaultValue)
2.在“最高级”的外层组件使用 Provider 组件提供context对象
import React, { Component } from "react";
import { MyContext } from "./context";
import Child from "./Child";
export default class Parent extends Component {
constructor(props) {
super(props);
// 声明一个state,用于共享数据,结构和context的defaultValue保持一致
this.state = {
count,
// 这是一个用于修改state数据的方法
add: this.addFn.bind(this),
};
}
addFn(val) {
const count = (this.state.count += val);
this.setState({ count });
}
render() {
return (
// 使用value属性来给Provider包着的组件提供数据,所有被它包着的组件都可以得到这这些数据
<Provider value={this.state}>
<Child />
</Provider>
);
}
}
3.在需要使用“全局”数据的地方使用Consumer组件获取并操作数据
import React, { Component } from "react";
import { MyContext } from "./context";
export default class Child extends Component {
render() {
return (
// Consumer组件可能帮我们得到对应的context里面的数据,Consumer组件里面必须使用一个函数返回jsx结构
<MyContext.Consumer>
{/* 这个函数的value参数就是Provider的value属性*/}
{(value) => (
<div>
{/* 获取数据 */}
<div>{value.count}</div>
<button
// 操作数据
onClick={() => {
value.add(1);
}}
>
++
</button>
</div>
)}
</MyContext.Consumer>
);
}
}
生命周期
React生命周期可以分成三个阶段:
1、实例化(挂载阶段):对象创建到完全渲染
2、存在期(更新期):组件状态的改变
3、销毁/清除期:组件使用完毕后,或者不需要存在与页面中,那么将组件移除,执行销毁。
1、实例化/挂载阶段
constructor()
componentWillMount()
render()
componentDidMount()
2、存在期/更新期
存在期:且件已经渲染好并且用户可以与它进行交互。通常是通过一次鼠标点击、手指点按者键盘事件来触发一个事件处理器。随着用户改变了组件或者整个应用的state,便会有新的state流入组件树,并且我们将会获得操控它的机会。
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
3、销毁期
componentWillUnmount() 销毁组件前做一些回收的操作
4、生命周期总结
React组件的生命周期 3大阶段10个方法 1、初始化期(执行1次) 2、存在期 (执行N次) 3、销毁期 (执行1次)
componentDidMount : 发送ajax异步请求
shouldComponentUpdate: 判断props或者state是否改变,目的:优化更新性能
hook
EffectHook
import React, { useEffect, useState } from "react";
export default function EffectHood() {
const [count, setCount] = useState(0);
// 依赖于数据的变化,数据一旦发生了变化就会被调用
useEffect(() => {
console.log("数据发生了变化");
}, [count]);
// 模拟数组的生命周期 模拟 didMount-组件挂载完成
useEffect(() => {
let h2 = document.querySelector("h2");
console.log(h2);
}, []);
// 模拟didUpdate生命周期 页面更新
useEffect(() => [console.log("模拟了didUpdate")], [count]);
// 模拟willUnmount
useEffect(() => {
return () => {
console.log("组件要被销毁了");
};
}, []);
return (
<div>
<h2>EffectHood</h2>
<p>count:{count}</p>
<p>
<button onClick={() => setCount(count + 1)}>按钮</button>
</p>
</div>
);
}
Refhook
/**
* 在react里面
* 获取class组件的实例,或者 dom 元素 ,就可以使用 RefHook
*
*/
import React, { Component, useRef } from "react";
export default function RefHook() {
const ref = useRef();
const ref2 = useRef();
// console.log(ref.current);
// 函数组件是没有生命周期的 - 等等会使用 useEffect 模拟生命周期
setTimeout(() => {
console.log(ref.current);
console.log(ref2.current);
}, 2000);
return (
<div>
<h4 ref={ref}>RefHook</h4>
{/* 函数组件是没有实例的,所以无法使用ref获取 */}
{/* <Child ref={ref2} /> */}
<Child2 ref={ref2} />
</div>
);
}
function Child() {
return <div>Child Function</div>;
}
class Child2 extends Component {
render() {
return <div>Child Class</div>;
}
}
StateHook
/**
* hook
* 一堆 react 提供的 函数 , 就是要学习这些函数 有什么用,怎么用
*
* useSate
* 作用: 实现响应式数据
* 用法:
* const [响应式数据,修改数据的方法] = useState(初始值)
*
*/
import React, { useState } from "react";
export default function StateHook() {
// 注意点: 1. hook只能在函数组件里面使用
const [count, setCount] = useState(10);
// 2. 只能在函数的最外层使用
// if (true) {
// const [count, setCount] = useState(10);
// }
return (
<div>
<h3>StateHook</h3>
<p>Count: {count}</p>
<p>
<button onClick={() => setCount(count + 1)}>按钮</button>
</p>
</div>
);
}
CusHook
/**
* 自定义hook
*
*/
import React, { useState } from "react";
// 自定义hook也是要以use开头的
function useCount() {
const [count, setCount] = useState(10);
const clickHandle = () => {
setCount(count + 1);
};
const add = () => {
const temp = count + 1 / 2 + 15 * 3.14;
setCount(temp);
};
return [count, clickHandle, add];
}
export default function CusHook() {
const [count, clickHandle, add] = useCount();
const [age, setAge] = useState(10);
const handle2 = () => {
setAge(age + 1);
};
// 将来在 函数组件里面的 useState 可能会越写越多,导致 函数组件特别的庞大
// 我们在使用hook的时候,还把一些复杂的逻辑,抽离出来 ,形成 自定义hook
return (
<div>
<h3>StateHook</h3>
<p>Count: {count}</p>
<p>
<button onClick={clickHandle}>按钮</button>
</p>
</div>
);
}
ReducerHook
import React, { useReducer, useState } from "react";
// export default function ReducerHook() {
// const [count, setCount] = useState(0);
// // 可能我们在一个页面上,对于这个数据的操作比较复杂
// const add = () => {
// setCount(count + 1);
// };
// const subtruct = () => {
// setCount(count - 1);
// };
// const cheng = () => {
// setCount(count * 2);
// };
// const chu = () => {
// setCount(count / 2);
// };
// return <div>ReducerHook</div>;
// }
// 使用自定义hook放到外面
// function useCount() {
// const [count, setCount] = useState(0);
// // 可能我们在一个页面上,对于这个数据的操作比较复杂
// const add = () => {
// setCount(count + 1);
// };
// const subtruct = () => {
// setCount(count - 1);
// };
// const cheng = () => {
// setCount(count * 2);
// };
// const chu = () => {
// setCount(count / 2);
// };
// return [count, add, subtruct, cheng, chu];
// }
// export default function ReducerHook() {
// const [count, add, subtruct, cheng, chu] = useCount();
// return <div>ReducerHook</div>;
// }
// 继续优化
// 我们只对外暴露两个东西,一个 响应式数据,一个是修改的方法
// function useCount() {
// const [count, setCount] = useState(0);
// // 可能我们在一个页面上,对于这个数据的操作比较复杂
// const add = () => {
// setCount(count + 1);
// };
// const subtruct = () => {
// setCount(count - 1);
// };
// const cheng = () => {
// setCount(count * 2);
// };
// const chu = () => {
// setCount(count / 2);
// };
// const fn = (type) => {
// // 在这个处理的函数里面,判断,你想如何操作数据
// if (type == "add") {
// add();
// } else if (type == "sub") {
// subtruct();
// }
// // ...
// };
// return [count, fn];
// }
// export default function ReducerHook() {
// const [count, fn] = useCount();
// return (
// <div>
// <h4>ReducerHook</h4>
// <p>count 是 : {count}</p>
// <p>
// <button onClick={() => fn("add")}>add</button>
// <button onClick={() => fn("sub")}>sub</button>
// </p>
// </div>
// );
// }
// 基于上述的逻辑,react里面帮我们封装好了一个 reducerHook
// 1. 先准备一个reducer函数
// state 是响应式数据 , action 是一个操作
const reducer = (state, action) => {
// 在reducer函数里面判断 action 是什么操作,然后进行对应的数据处理
switch (action.type) {
case "add":
state.count++;
break;
default:
break;
}
// reducer 函数一定要返回一个state
// 每次返回的state,一定要保证是一个新的引用
return { ...state };
};
export default function ReducerHook() {
// 2.在函数组件里面使用 useReducer 实现上面的代码
// dispatch是修改响应式数据的委托
const [data, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<h4>ReducerHook</h4>
<p>count : {data.count}</p>
<p>
{/* dispatch({type}) */}
<button onClick={() => dispatch({ type: "add" })}>++</button>
</p>
</div>
);
}