无缝切换?从Vue到React
阿里妹导读
本文主要针对Vue开发者或新手快速上手React。 本文主要分析Vue和React在开发上的区别,帮助Vue开发者快速上手React,同时也适用于前端新手入门React。
单文件组件 VS class组件 VS 函数组件
Vue: 单文件组件React: Class组件<template><div>{{ greeting }} world</div></template><script>export default {data() {return {greeting: 'hello'}}}</script><style></style>
官方文档[1]class Comp extends Component {constructor(props) {super(props);this.state = {greeting: 'hello'};}render() {return (<div><div>{ greeting } world</div></div>);}}
React: 函数组件(推荐) 在Vue单文件组件和React的Class组件中,我们的元素、数据变量等必须放到固定的位置,以一种固定的格式书写,而在函数组件中书写方式变得更简单,我们可以 像写函数一样 写组件。更重要的是,这样就不用关心那些难理解的 this 了。
官方文档[2]const Comp = () => {const [greeting, setGreeting] = useState('hello');return (<div><div>{ greeting } world</div></div>)}
双向绑定 VS 单向数据流
在Vue中我们使用v-bind、v-modal对数据进行绑定,无论是来自用户操作导致的变更,还是在某个方法里赋值都能够 直接更新 数据,不需要手动进行update操作。this.data.greeting = "Hello"
而在React里需要调用
set方法
更新,当React感应到set触发时会再次调用render对dom进行刷新,虽然有些麻烦但这种方式可以让数据变化更加清晰易追寻。
this.state.greeting = "Hello" // 错误写法this.setState({greeting: 'Hello'}); // 正确写法✅setGreeting('Hello'); // 来自hooks的set写法 后面会介绍
React的大buff:JSX
初次接触JSX的开发者可能会感觉JSX结构混乱,因为你可以在dom元素内部直接写js逻辑,也可以在js逻辑里写dom,这就像把html和js混在一起:虽然元素和逻辑的边界模糊了,但我们的组件会变得更加灵活,这样能够将一个组件分成不同的模块,当需要修改是时我们只需关注对应的函数,不用担心影响到其他部分,这对 复杂的页面结构 非常有用。import getUserType from './getUserType'const Comp = () => {const [greeting, setGreeting] = useState('hello');const Button = () => {const userType = getUserType()if(userType === 0) {return <button>去购买</button>}if(userType === 1) {return <button>去充值</button>}return null}return (<div><div>{ greeting } world</div>{Button()}</div>)}
Hooks
是什么 上面我们在讲数据流的时候有提到,处理数据的两种方式其中第二种方式的useState就是Hooks中的一种,是比较常用的Hook,除此之外还有useEffect,useRef等,每个都有着不同的功能。// 方式1this.state = {greeting: 'Hello'}this.setState({greeting: 'Hello'});// 方式2const [greeting, setGreeting] = useState('hello');setGreeting('Hello');
为什么用 逻辑独立 以数据更新为例,简单来讲,如果不用Hooks,每次更新数据都用 setSate ,我们的代码里就会出现很多setState调用,setState根据入参可以一次修改一个字段,也可以一次修改多个字段,想要知道某个数据在哪里被做了怎样的修改就会变的很麻烦,甚至可能不小心多写一个字段修改了不该修改的数据。而用Hooks的 useState 的话,因为它在定义时会对字段创建其 专用 的修改函数,所以只要有这个函数的调用,就代表这个字段被做了修改。
怎么用 常用Hooks(Hooks只能在的 函数组件 内使用):
1、 useState: 用于定义组件的 State,相当于 this.state=xxx 或者Vue里的 data(){return xxx}
2、 useEffect:通过依赖变更触发的钩子函数 ,类似Vue的 watcherconst [greeting, setGreeting] = useState('hello'); // greeting 默认 hello// 点击greeting变为Hello1<div onClick={setGreeting('Hello1')}>{greeting}</div>
3、 useRef: 返回ref对象, .current 可以获取到其原生的元素本身// 当userId变化时调用refreshuseEffect(() => {refresh();}, [userId]);// 进入页面时会执行init, 退出时会执行destroyuseEffect(() => {init();return () => {destroy()}}, []);
const el = useRef(null);<div ref={el}></div>// console.log(el.current.offsetHeight) 返回div的offsetHeight
状态管理
是什么?为什么用? 官方定义:“集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化”。 举个例子,页面里两个组件需要展示/更新userName,如果不使用状态管理,我们可以用父子组件交互的方式把userName字段和 setUserName 函数作为组件参数传入两个组件中,调用setUserName即触发page更新userName:
// store.jsimport { createStore } from 'vuex'const store = createStore({state: {count: 0},mutations: {setCount (state, value) {state.count = value}},actions: {addon ({ commit, state }) {const count = state.countcommit('set', count+1)}}})
React:不止是Redux React本身并不带状态管理,状态管理对于React更像是一种普通的第三方工具,工作中不同项目可能用了Redux、mobx、rematch等不同的状态管理工具,不同工具写法会有所区别,使用者要自己区分学习,除此之外一些脚手架会自带状态管理,写法会简单些,比如Rax,为方便理解接下来以Rax的写法进行说明。// index.jsimport App from './vue'import { createApp } from 'vue'const app = createApp(App).mount('#app');// 将 store 实例作为插件安装app.use(store)// index.vue<template><div>{{ this.$store.state.count }} world</div></template><script>export default {methods: {increment() {this.$store.commit('setCount', 10)this.$store.dispatch('setCount')console.log(this.$store.state.count)}}}</script><style></style>
与上面所说的Vuex的state、mutations、actions对应,React里叫做 state、reducers、effects 。 state 负责定义数据, reducers 负责修改数据, effects 负责利用 reducers 做一些复杂异步操作,下面示例解释的更清楚:
// src/pages/Dashboard/models/counter.tsconst delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));export default {// 定义 model 的初始 statestate: {count: 0},// 定义改变该模型状态的纯函数reducers: {increment(prevState) {return { count: prevState.count + 1 };},},effects: (dispatch) => ({async incrementAsync() {await delay(10);dispatch.counter.increment();},}),}
// src/pages/Dashboard/store.tsimport { createStore } from 'rax-app';import counter from './models/counter';const store = createStore({ counter });export default function Dashboard() {// 使用 counter 模型const [counterState, counterDispatchers] = store.useModel('counter');return (<><span>{counterState.count}</span><button onClick={counterDispatchers.increment}>+</button><button onClick={counterDispatchers.incrementAsync}>+</button></>);}
React代码实战:开发一个TodoList
// index.jsximport $i18n from '@alife/panda-i18n';import React, { useCallback } from 'react';import { connect } from 'react-redux';import { Link } from '@ice/router';import PropTypes from 'prop-types';import { Form, Input } from 'cn-next';import styles from './index.module.scss';const FormItem = Form.Item;const AddTodo = (props) => {const { onAdd } = props;const onSubmit = useCallback((values, errors) => {if (!errors) {onAdd(values.text);}},[onAdd],);return (<div x-class={[styles.add]}><Form className={styles.addForm} inline onSubmit={onSubmit}><FormItemclassName={styles.addItem}requiredrequiredMessage={$i18n.get({id: 'EnterAToDoList.other',dm: '请输入待办事项',})}><Inputname='text'placeholder={$i18n.get({id: 'EnterAToDoList.other',dm: '请输入待办事项',})}/></FormItem><Form.Submit className={styles.addSubmit} onClick={onSubmit} validate>{$i18n.get({ id: 'Add.other', dm: '添加' })}</Form.Submit></Form></div>);};AddTodo.propTypes = {onAdd: PropTypes.func,};AddTodo.defaultProps = {onAdd: () => {},};const Todos = (props) => {const { list, createAsync } = props;// 添加const onAddTodo = useCallback(async (text) => {await createAsync(text);},[createAsync],);return (<div className={styles.todos}><AddTodo onAdd={onAddTodo} /><div className='mb-30'>{list.map((item) => {return (<div key={item.text} className={styles.todo}><span>{item.text}</span></div>);})}</div><div><Link to='/'>{$i18n.get({ id: 'ReturnToHomePage.other', dm: '返回首页' })}</Link></div></div>);};Todos.propTypes = {list: PropTypes.arrayOf(PropTypes.shape({})).isRequired,createAsync: PropTypes.func.isRequired,};const mapState = (state) => ({list: state.todos.list,});const mapDispatch = (dispatch) => ({createAsync: dispatch.todos.createAsync,doneAsync: dispatch.todos.doneAsync,undoneAsync: dispatch.todos.undoneAsync,});export default connect(mapState, mapDispatch)(Todos);
// todo.jsexport const todos = {state: {list: [{text: 'Learn typescript',done: true,},{text: 'Try immer',done: false,},],},reducers: {create(state, text) {state.list.push({ text, done: false });return state;},done(state, idx) {if (state.list[idx]) {state.list[idx].done = true;}return state;},undone(state, idx) {if (state.list[idx]) {state.list[idx].done = false;}return state;},},effects: (dispatch) => ({async createAsync(payload) {// 模拟异步操作await new Promise((resolve) => setTimeout(resolve, 250));dispatch.todos.create(payload);},async doneAsync(payload) {// 模拟异步操作await new Promise((resolve) => setTimeout(resolve, 250));dispatch.todos.done(payload);},async undoneAsync(payload) {// 模拟异步操作await new Promise((resolve) => setTimeout(resolve, 250));dispatch.todos.undone(payload);},}),};
参考链接: [1]https://zh-hans.reactjs.org/docs/components-and-props.html#function-and-class-components [2]https://zh-hans.reactjs.org/docs/components-and-props.html#function-and-class-components
寻找百位乘风者伯乐!邀好友入驻可享多重好礼
即日起邀请身边的朋友、同事一起加入乘风者计划并成为阿里云开发者社区的新入驻博主,邀请人数前100位即可获得乘风者伯乐奖,您将有机会获得Redmi Note 11 5G手机、保温杯、鼠标等奖品。同时您所邀约的博主将有机会获得乘风者权益。
点击阅读原文查看详情。