react学习对比Vue
React 和Vue
React摆脱了传统的JS+CSS+HTML,React=Think in JS+Data in JS+View in JS。React遵循的是函数式编程
,react最大的特性(核心思想)就是==组件化
==。万物皆可组件化,对于代码的耦合是极大的减小的。
Vue遵循MVVM的设计思想模式,将Data和View独立出,双方只需要关注视图或者数据即可,双方通过viewModel来构成双方沟通的桥梁。model层(数据模型)只需要重数据,view层更多的就是关注界面视图。
组件化
类组件(★★)
通过导入react中的component创建组件。
1.组件类必须继承React.Component类
2.必须要有render方法;
3.render方法的return返回的就是组件的内容
import React, { Component } from 'react';
class App extends Component {
constructor() {
super()
}
//render返回渲染DOM
render() {
return <h2>Hello App</h2>
}
}
export default App
函数组件
函数组件是没有生命周期的,
函数组件也是没有内部状态(state),只有props,但是React16.8新增HOOK,使得函数组件中也可以添加state
没有内部的this,打印的是undefined
export default function App(props) {
//没有render()钩子函数,需要通过return来返回结果
return (
<div>Hello World</div>
)
}
<APP sta=“sss” sat2={data}/>
根节点
react和Vue同时都需要一个根节点
所以就需要添加一个 <div ></div>
,但是有时候我们在组件中往往不想添加这个div节点,又想要一个根节点,这时候,react中提供了*Reac.Fragement*
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
Dom渲染时候并不会将Fragement渲染出来
Fragement有简写
<>
//内容
</>
列表 & key
如果不对每个元素设置key属性,就会看到一个警告 a key should be provided for list items
,而且最好用数据中的id作为key值。
function NumberList(props) {
//props接受组件中传过来的属性参数
const numbers = props.numbers;
//对于经过便利循环得到的列表,需要给每一个元素分配一个key
const listItems = numbers.map((numbers) =>
<li key={number.toString()}>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
//render()函数渲染
ReactDOM.render(
//组件列表
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Vue
<div v-for="(v,i) in list" key="i" />
state && Props
props 是组件对外的接口,state 是组件对内的接口
state
State是可变的,是一组用于反映组件UI变化的状态集合。相当于Vue中的data函数数据
class State extends React.Component {
constructor() {
super();
//初始化state
//在组件内部是唯一数据源
this.state = {data: "string"};
}
render() {
return (
<div>
<h2>It is {this.state.data}.</h2>
</div>
);
}
}
如果要改变state中的数据,直接从this.state.data="change string"是无法直接更新vie视图的。
正确的修改方式应该是使用setState()
this.setState({data: 'change string'});
同时 state的更新是异步更新,所以不能依赖当前的State来计算下一个State。
// 错误 依赖的this.state并不能保证是最新的State
this.setState({
counter: this.state.counter + this.props.increment,
});
// 正确 把值当成参数
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
setState()(★★★)
更新数据
setState()
更新数据是异步的- 注意:使用该语法,后面的
setState
不要依赖前面setState
的值 - 多次调用
setState
,只会触发一次render 没有导致state的值发生变化的setState会导致重渲染(render函数重复执行)
推荐语法
第二个参数
- 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
- 语法:
setState(update[,callback])
props(★★★)
- 组件时封闭的,要接受外部数据应该通过props来实现
- props的作用:接收传递给组件的数据
- 传递数据:给组件标签添加属性
而Props对于使用它的组件来说,是只读的,要想修改Props,只能通过该组件的父组件修改。
在组件状态上移的场景中,父组件正是通过子组件的Props, 传递给子组件其所需要的状态。
React中的props与Vue中的props使用方法相似
function Welcome(props) {
//调用props获取到组件传送的属性值
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
//name属性可以被props.name获取到,name除了普通数据,还可以传函数
<Welcome name="Sara" />
<Welcome name="Cahal" />
</div>
);
}
//render()函数实现渲染
ReactDOM.render(
<App />,
document.getElementById('root')
);
children属性
- children属性: 表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
- children属性与普通的props一样,值可以使任意值(文本、react元素、组件、甚至是函数)
propTypes(★)
- 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据,简单来说就是组件调用者可能不知道组件封装着需要什么样的数据
- 如果传入的数据不对,可能会导致报错
- 关键问题:组件的使用者不知道需要传递什么样的数据
- props校验:允许在创建组件的时候,指定props的类型、格式等
propTypes可以用来做类型的校验,如果后面有isRequired ,组件中的数据必须要求传递
import PropTypes from 'prop-types'
组件名.propTypes = {
test: PropTypes.string.isRequired,//isRequired必须传递
content: PropTypes.string,
deleteIntem: PropTypes.func,
index: PropTypes.number
}
defaultProps
默认值,与上的test属性相结合,test是一个必填的值,当父组件没有传递test值时,则会自动调用defaultProps方法
TodoItem.defaultProps = {
test: 'hello world'
}
Refs
refs的主要应用场景:
需要直接操作DOM的一些操作:
- 失去/获取焦点,文本选择或媒体播放
- 通过DOM完成一些简单动画效果
- 集成第三方 DOM 库
react中的Refs是使用React.createRef() 创建的,
class MyComponent extends React.Component {
constructor(props) {
super(props);
//创建一个空白ref
this.myRef = React.createRef();
}
render() {
//通过ref属性附加到React中
return <div ref={this.myRef} />;
}
}
构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
所以调用ref时候需要:
focusTextInput() {
// 注意:我们通过 "current" 属性来获得DOM节点的焦点
this.textInput.current.focus();
}
}
但是React.createRef() 只适合class组件。对于函数组件需要useRef()
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);
//通过得到的refs执行DOM操作
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
//绑定refs
ref={textInput}
//获取焦点操作
onClick={handleClick}/>
</div>
);
}
Vue
//绑定ref
<input type='text' ref='input1' />
methods:{
add:function(){
//this.$refs.input1 获取dom节点
this.$refs.input1.value ="10";
}
}
生命周期钩子函数
挂载
创建时候会依次执行constructor(),render(),componentDidMount()
- render函数并不做实际的渲染动作,它只负责返回一个JSX描述的结构,最终由React来操作渲染过程。
1. constructor(props):初始化钩子函数
完成React数据初始化,数据已经可以被调用,相当于Vue生命周期中的created
class Clock extends React.Component {
constructor(props) {
//完成数据初始化,State和Props中的数据已经可以被访问
super(props);
this.state = {date: new Date()};
}
}
2. componentWillMount()
使用场景较少,在未来react的更新中将会被逐渐废弃。
主要应用场景是在服务端渲染时使用,代表:数据初始化完成,但DOM还未被渲染。相当于Vue中的beforeMounted()
3. componentDidMount()挂载
使用次数最多
组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
class Clock extends React.Component {
componentDidMount() {
//挂载
//执行数据初始化的相关操作,异步请求,定时器设置
}
}
更新
4. shouldComponentUpdate()更新
主要应用场景:
没有导致state的值发生变化的setState会导致重渲染(render函数重复执行)
这会造成操作冗余,影响性能
shouldComponentUpdate函数是重渲染时render()函数调用前被调用的函数,它接受两个参数:nextProps和nextState,分别表示下一个props和下一个state的值。
当函数返回false时候,阻止接下来的render()函数的调用,阻止组件重渲染,而返回true时,组件照常重渲染。
//在render函数调用前判断:
//如果前后state中Number不变,通过return false阻止render调用
shouldComponentUpdate(nextProps,nextState){
if(nextState.Number == this.state.Number){
return false
}
}
5. componentDidUpdate(prevProps, prevState)
此方法在组件更新后被调用 ,可以操作组件更新的DOM,prevProps和prevState这两个参数指的是组件更新前的props和state。
用于进行判断,数据是否更新成功,防止因数据未更新造成的页面过度刷新操作。
//页面更新数据之后需要调用这个生命周期componentDidUpdate
componentDidUpdate(prevProps, prevState){
//更新后的数据
let pageSize = this.state.pageSize;
if(prevProps.pagesize ===pageSize){
//判断数据是否发生更新,执行相关操作
}
}
卸载
执行时机:组件从页面中消失
作用:用来做清理操作
6. componentWillUnmount ()卸载
组件销毁钩子,常用
在此处完成组件的卸载和数据的销毁。
常用于清楚计数器(clearTimeout, clearInterval),移除所有组建中的监听( removeEventListener).
componentWillUnmount() {
clearInterval(this.timerID);
}
HOOK
Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook
State Hook
在函数组件中,我们没有 this,所以我们不能分配或读取 this.state。我们直接在组件中调用 useState Hook:
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>
Click me
</button>
}
Effect Hook
useEffect可以让函数组件模拟class组件的生命周期
函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
使用 effect hook 将生命周期“钩”到纯函数中
我们可以把 useEffect
Hook 看做 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
**useEffect
在默认情况下,它在第一次渲染之后和每次更新之后都会执行。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [name, setName] = useState(0);
useEffect(() => {
//用来模拟class组件中的componentDidMount,componentDidUpdate和componentWillUnMount
console.log('数据初始化渲染和更新数据在这里实现')
//相当于生命周期中的componentWillUnmount钩子函数
return() => {
//清除定时器
window.clearInterval(timeinter)
}
},[count, name]);
});
return (
<div>
<button onClick={()=> setCount(count+ 1)}>Click</button>
</div>
);
}
此时相当于componentDidMount + componentDidUpdate钩子的组合,初次渲染并且状态改变时都会触发
useEffect( ()=>{
console.log('数据初始化渲染会执行')
console.log('数据发生更新也会执行该函数')
} )
我们传入 [count] 作为第二个参数,如果 count 的值是 5,而且我们的组件重渲染的时候 count 还是等于 5,React 将对前一次渲染的 [5] 和后一次渲染的 [5] 进行比较。因为数组中的所有元素都是相等的(5 === 5),React 会跳过这个 effect,这就实现了性能的优化。
useEffect(()=>{
console.log('count值发生变化就会执行该effect')
console.log('count值不变,数据更新后就跳过执行这个effect')
},[count])
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。
useEffect(() => {
console.log("只在初次componentDidMount时执行一次")
}, []);
useEffect中返回一个函数
,这个函数相当于componentWillUnmount
钩子, 卸载组件的时候触发
useEffect(()=>{
console.log('数据初始化渲染会执行')
console.log('数据发生更新也会执行该函数')
return ()=>{
console.log("组件将要被卸载了")
}
})
组件传值
父组件向子组件传值
父组件在子组件中添加了属性,传入到child组件中的props的count属性
class Father extends React.Component {
constructor(props){
super(props)
// 提供共享的状态
state = {
count: 0
}
}
render() {
return (<div>
{/* 把状态提供给第一个子组件 */}
<Child count={this.state.count}/>
</div>
)
}
}
子组件通过this.props获取到渲染组件传入的属性参数
class Child extends React.Component {
constructor(props){
super(props)
}
render() {
return (
<h1>计数器:{this.props.count}</h1>
)
}
}
子组件向父组件传值
- 利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
- 父组件提供一个回调函数,用来接收数据
- 将该函数作为属性的值,传递给子组件
class Parent extends React.Component{
getChildMsg = (msg) => {
console.log('接受到的子组件数据',msg);
}
render(){
return(
<>
<Child getMsg={this.getChildMsg}/>
</>
)
}
}
子组件通过props返回数据
class Child extends React.Component{
state={value:'childMsg'}
handleClick = () => {
this.props.getMsg(this.state.value)
}
render(){
return(
<>
<button OnClick={this.handleClick}></button>
</>
)
}
}
兄弟之间共享数据
class Counter extends React.Component {
// 提供共享的状态
state = {
count: 0
}
// 提供共享方法
onIncrement = (res) => {
// 只要第二个子组件调用了这个函数,就会执行里面代码
this.setState({
count: this.state.count + res
})
}
render() {
return (<div>
{/* 把状态提供给第一个子组件 */}
<Child1 count={this.state.count}/>
{/* 把共享方法提供给第二个子组件 */}
<Child2 onIncrement={this.onIncrement} />
</div>
)
}
}
Child1
- 在第一个子组件里面就能通过props获取到
class Child1 extends React.Component {
render() {
return (
<h1>计数器:{this.props.count}</h1>
)
}
}
Child2
- 在父组件中提供共享方法,通过属性传递给第二个子组件,方便第二个子组件来进行调用
class Child2 extends React.Component {
handleClick = () => {
// 这里一旦调用,就会执行父组件里面 onIncrement函数
this.props.onIncrement(2)
}
render() {
return (
<button onClick={this.handleClick}>+</button>
)
}
}
Context(★★)
- 如果两个组件相隔层级比较多,可以使用Context实现组件通讯
- Context提供了两个组件:Provider 和 Consumer
- Provider组件: 用来提供数据
- Consumer组件: 用来消费数据
应用场景: 跨组件传输数据,多适用于父->子->孙的嵌套传值 - 调用
React.createContext()
创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件
const {Provider, Consumer} = React.createContext()
//通过Provider传值
<Provider value="dark">
//Toobar 中的子组件就不需要不停的传递props
<Toolbar />
</Provider>
- 哪一层想要接收数据,就用Consumer进行包裹,在里面回调函数中的参数就是传递过来的值
render() {
return <Button theme={this.context} />;
}
ThemedButton.contextType = ThemeContext;
//or 第二种写法
<Consumer>
Consumer的children必须是一个函数,
//传递的等于组件树中层这个 context 最接近的 Provider 的对应属性
{theme =><Button theme={theme} />; // 核心代码}
</Consumer>
高阶组件(★★★)
组件是对一些功能进行封装,而高阶组件则是对组件的一种包装,加强组件的功能。相当于手机壳是对手机的一种包装。
// 定义一个函数,在函数内部创建一个相应类组件
//普通函数,参数数量自定义
function withMouse(WrappedComponent,data) {
// 该组件提供复用状态逻辑
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
// 事件的处理函数
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 当组件挂载的时候进行事件绑定
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
// 当组件移除时候解绑事件
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
// 在render函数里面返回传递过来的组件,把当前组件的状态设置进去
return <WrappedComponent {...this.state} />
}
}
return Mouse
}
哪个组件需要加强,通过调用withMouse
这个函数,然后把返回的值设置到父组件中即可
function Position(props) {
return (
<p>
X:{props.x}
Y:{props.y}
</p>
)
}
// 把position 组件来进行包装
let MousePosition = withMouse(Position)
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
高阶组件
<MousePosition></MousePosition>
</div>
)
}
}
render Props(★★)
render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.state={x:123,y:225}
}
render() {
return (
//公共的渲染部分
<div style={{ height: '100vh' }}>
/*
*使用“render”道具来动态决定渲染什么
*/
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>render Props 渲染</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}