React.js和Vue.js都是很好的框架。而且Next.js和Nuxt.js甚至将它们带入了一个新的高度,这有助于我们以更少的配置和更好的可维护性来创建应用程序。但是,如果你必须经常在框架之间切换,在深入探讨另一个框架之后,你可能会轻易忘记另一个框架中的语法。在本文中,我总结了这些框架的基本语法和方案,然后并排列出。我希望这可以帮助我们尽快掌握语法,不过限于篇幅,这篇文章只比较React.js和Vue.js,下一篇再谈Next.js个Nuxt.js。
Github:https://github.com/oahehc/react-vue-comparison
React.js
ReactDOM.render(<App />, document.getElementById("root"));
Vue.js
new Vue({ render: (h) => h(App), }).$mount("#root");
Class component
class MyReactComponent extends React.Component { render() { return <h1>Hello world</h1>; } }
Function component
function MyReactComponent() { return <h1>Hello world</h1>; }
<template> <h1>Hello World</h1> </template> <script> export default { name: "MyVueComponent", }; </script>
React.js
function MyReactComponent(props) { const { name, mark } = props; return <h1>Hello {name}{mark}</h1>; } MyReactComponent.propTypes = { name: PropTypes.string.isRequired, mark: PropTypes.string, } MyReactComponent.defaultProps = { mark: '!', } ... <MyReactComponent name="world">
Vue.js
<template> <h1>Hello {{ name }}</h1> </template> <script> export default { name: "MyVueComponent", props: { name: { type: String, required: true, }, mark: { type: String, default: "!", }, }, }; </script> ... <MyVueComponent name="World" />
Class component
class MyReactComponent extends React.Component { save = () => { console.log("save"); }; render() { return <button onClick={this.save}>Save</button>; } }
Function component
function MyReactComponent() { const save = () => { console.log("save"); }; return <button onClick={save}>Save</button>; }
<template> <button @click="save()">Save</button> </template> <script> export default { methods: { save() { console.log("save"); }, }, }; </script>
React.js
function MyItem({ item, handleDelete }) { return <button onClick={() => handleDelete(item)}>{item.name}</button>; /* * 应用useCallback钩子来防止在每次渲染时生成新的函数。 * * const handleClick = useCallback(() => handleDelete(item), [item, handleDelete]); * * return <button onClick={handleClick}>{item.name}</button>; */ } ... function App() { const handleDelete = () => { ... } return <MyItem item={...} handleDelete={handleDelete} /> }
Vue.js
<template> <button @click="deleteItem()">{{item.name}}</button> </template> <script> export default { name: "my-item", props: { item: Object, }, methods: { deleteItem() { this.$emit("delete", this.item); }, }, }; </script> ... <template> <MyItem :item="item" @delete="handleDelete" /> </template> <script> export default { components: { MyItem, }, methods: { handleDelete(item) { ... } }, }; </script>
Class component
class MyReactComponent extends React.Component { state = { name: 'world, } render() { return <h1>Hello { this.state.name }</h1>; } }
Function component
function MyReactComponent() { const [name, setName] = useState("world"); return <h1>Hello {name}</h1>; }
<template> <h1>Hello {{ name }}</h1> <!-- 使用组件状态作为prop --> <my-vue-component :name="name"> </template> <script> export default { data() { return { name: "world" }; }, }; </script>
Class component
class MyReactComponent extends React.Component { state = { count: 0, }; increaseCount = () => { this.setState({ count: this.state.count + 1 }); // 在更新之前获取当前状态,以确保我们没有使用陈旧的值 // this.setState(currentState => ({ count: currentState.count + 1 })); }; render() { return ( <div> <span>{this.state.count}</span> <button onClick={this.increaseCount}>Add</button> </div> ); } }
Function component
function MyReactComponent() { const [count, setCount] = useState(0); const increaseCount = () => { setCount(count + 1); // setCount(currentCount => currentCount + 1); }; return ( <div> <span>{count}</span> <button onClick={increaseCount}>Add</button> </div> ); }
<template> <div> <span>{{count}}</span> <button @click="increaseCount()">Add</button> </div> </template> <script> export default { data() { return { count: 0 }; }, methods: { increaseCount() { this.count = this.count + 1; }, }, }; </script>
React没有双向绑定,因此我们需要自己处理数据流
function MyReactComponent() { const [content, setContent] = useState(""); return ( <input type="text" value={content} onChange={(e) => setContent(e.target.value)} /> ); }
<template> <input type="text" v-model="content" /> </template> <script> export default { data() { return { content: "" }; }, }; </script>
React.js没有计算属性,但我们可以通过react hook轻松实现
function DisplayName({ firstName, lastName }) { const displayName = useMemo(() => { return `${firstName} ${lastName}`; }, [firstName, lastName]); return <div>{displayName}</div>; } ... <DisplayName firstName="Hello" lastName="World" />
<template> <div>{{displayName}}</div> </template> <script> export default { name: "display-name", props: { firstName: String, lastName: String, }, computed: { displayName: function () { return `${this.firstName} ${this.lastName}`; }, }, }; </script> ... <DisplayName firstName="Hello" lastName="World" />
React.js没有 watch
属性,但是我们可以通过react hook轻松实现
function MyReactComponent() { const [count, setCount] = useState(0); const increaseCount = () => { setCount((currentCount) => currentCount + 1); }; useEffect(() => { localStorage.setItem("my_count", newCount); }, [count]); return ( <div> <span>{count}</span> <button onClick={increaseCount}>Add</button> </div> ); }
<template> <div> <span>{{count}}</span> <button @click="increaseCount()">Add</button> </div> </template> <script> export default { data() { return { count: 0 }; }, methods: { increaseCount() { this.count = this.count + 1; }, }, watch: { count: function (newCount, oldCount) { localStorage.setItem("my_count", newCount); }, }, }; </script>
React.js
function MyReactComponent({ children }) { return <div>{children}</div>; } ... <MyReactComponent>Hello World</MyReactComponent>
Vue.js
<template> <div> <slot /> </div> </template> <script> export default { name: "my-vue-component", }; </script> ... <MyVueComponent>Hello World</MyVueComponent>
React.js
function MyReactComponent() { return <div dangerouslySetInnerHTML={{ __html: "<pre>...</pre>" }} />; }
Vue.js
<template> <div v-html="html"></div> </template> <script> export default { data() { return { html: "<pre>...</pre>", }; }, }; </script>
React.js
function MyReactComponent() { const [isLoading, setLoading] = useState(true); return ( <div> {isLoading && <span>Loading...</span>} {isLoading ? <div>is loading</div> : <div>is loaded</div>} </div> ); }
Vue.js
<template> <div> <!--v-show: 总是渲染,但根据条件更改CSS--> <span v-show="loading">Loading...</span> <div> <div v-if="loading">is loading</div> <div v-else>is loaded</div> </div> </div> </template> <script> export default { data() { return { loading: true }; }, }; </script>
React.js
function MyReactComponent({ items }) { return ( <ul> {items.map((item) => ( <li key={item.id}> {item.name}: {item.desc} </li> ))} </ul> ); }
Vue.js
<template> <ul> <li v-for="item in items" :key="item.id"> {{item.name}}: {{item.desc}} </li> </ul> </template> <script> export default { props: { items: Array, }, }; </script>
React.js
function Modal({children, isOpen}) { const [isModalOpen, toggleModalOpen] = useState(isOpen); return ( <div className={isModalOpen ? 'open' : 'close'}> {type children === 'function' ? children(toggleModalOpen) : children} </div>) ; } Modal.propTypes = { isOpen: PropTypes.bool, children: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, } Modal.defaultProps = { isOpen: false, } ... <Modal isOpen> {(toggleModalOpen) => { <div> <div>...</div> <button onClick={() => toggleModalOpen(false)}>Cancel</button> </div> }} </Modal>
Vue.js(slot)
<template> <div v-show="isModalOpen"> <slot v-bind:toggleModal="toggleModalOpen" /> </div> </template> <script> export default { name: "modal", props: { isOpen: { type: Boolean, default: false, }, }, data() { return { isModalOpen: this.isOpen, }; }, methods: { toggleModalOpen(state) { this.isModalOpen = state; }, }, }; </script> ... <Modal isOpen> <template v-slot="slotProps"> <div>...</div> <button @click="slotProps.toggleModal(false)">Close</button> </template> </Modal>
Class component
class MyReactComponent extends React.Component { static getDerivedStateFromProps(props, state) {} componentDidMount() {} shouldComponentUpdate(nextProps, nextState) {} getSnapshotBeforeUpdate(prevProps, prevState) {} componentDidUpdate(prevProps, prevState) {} componentWillUnmount() {} render() { return <div>Hello World</div>; } }
Function component
function MyReactComponent() { // componentDidMount useEffect(() => {}, []); // componentDidUpdate + componentDidMount useEffect(() => {}); // componentWillUnmount useEffect(() => { return () => {...} }, []); // 在渲染之后但在屏幕更新之前同步运行 useLayoutEffect(() => {}, []); return <div>Hello World</div>; }
<template> <div>Hello World</div> </template> <script> export default { beforeCreate() {}, created() {}, beforeMount() {}, mounted() {}, beforeUpdate() {}, updated() {}, beforeDestroy() {}, destroyed() {}, }; </script>
React.js
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { // 更新状态,这样下一个渲染将显示回退UI。 return { hasError: true }; } componentDidCatch(error, errorInfo) {} render() { if (this.state.hasError) return <h1>Something went wrong.</h1>; return this.props.children; } } ... <ErrorBoundary> <App /> </ErrorBoundary>
Vue.js
const vm = new Vue({ data: { error: "", }, errorCaptured: function(err, component, details) { error = err.toString(); } }
Class component
class AutofocusInput extends React.Component { constructor(props) { super(props); this.ref = React.createRef(); } state = { content: "", }; componentDidMount() { this.ref.current.focus(); } setContent = (e) => { this.setState({ content: e.target.value }); }; render() { return ( <input ref={this.ref} type="text" value={this.state.content} onChange={this.setContent} /> ); } }
Function component
function AutofocusInput() { const [content, setContent] = useState(""); const ref = useRef(null); useEffect(() => { if (ref && ref.current) { ref.current.focus(); } }, []); return ( <input ref={ref} type="text" value={content} onChange={(e) => setContent(e.target.value)} /> ); }
<template> <input ref="input" type="text" v-model="content" /> </template> <script> export default { name: "autofocus-input", data() { return { content: "" }; }, mounted() { this.$refs.input.focus(); }, }; </script>
PureComponent
class MyReactComponent extends React.PureComponent { ... }
shouldComponentUpdate
class MyReactComponent extends React.Component { shouldComponentUpdate(nextProps) {...} ... }
React.memo
export default React.memo( MyReactComponent, (prevProps, nextProps) => { ... } );
useMemo
export default function MyReactComponent() { return React.useMemo(() => { return <div>...</div>; }, []); }
useCallback
function MyItem({ item, handleDelete }) { const handleClick = useCallback(() => handleDelete(item), [ item, handleDelete, ]); return <button onClick={handleClick}>{item.name}</button>; }
v:once
<span v-once>This will never change: {{msg}}</span>
函数式组件:我们可以将组件标记为 functional
,这意味它无状态 (没有响应式数据),也没有实例 (没有 this
上下文)。
<template functional> <h1>Hello {{ name }}</h1> </template> <script> export default { name: "MyVueComponent", props: { name: String, }, }; </script>
keep-alive 组件
<keep-alive> <component :is="view"></component> </keep-alive>
完。
如果对你有所启发和帮助,可以点个关注、收藏、转发,也可以留言讨论,这是对作者的最大鼓励。
作者简介:Web前端工程师,全栈开发工程师、持续学习者。