Loading...
React学习文档
6/28/2024, 10:22:24 AM
开发
新版文档排版、内容表达上自然更好,但是旧版本文档相对而言更全面
react组件的state
类似vue组件data
,存储组件自身的状态值;
而props和vue组件的props
类似,用于存储传递过来的配置项。
因此不论vue还是react,组件也可以被是为一个状态机
。
父组件:
import React from 'react'; import Children from './children.jsx'; export default class Parent extends React.Component { constructor(props) { super(); // 第一步: 一定是调用父级构造函数 this.state = { firstName: 'John', lastName: 'Simth', }; } render() { return ( <div> <p>my name is {this.state.firstName + '-' + this.state.lastName} </p> <Children lastName={this.state.lastName}></Children> </div> ); } }
子组件:
import React from 'react'; export default class Child extends React.Component { constructor(props) { super(props); // 存储自身状态可以也可以起其他名字,不一定是state, 但是那样setState 就设置不了这个状态值 this.stateChild = { frstName: 'Tom', }; // 但是配置项 props名字是锁死的,一定是这个 this.showMyself = this.showMyself.bind(this); // 绑定this 或者使用箭头函数 } showMyself() { console.log(this.stateChild.frstName + '-' + this.props.lastName); console.log(this.setStateChild, this.setState); this.setState({ name: 123, }); } tellMeSomething() { console.log(`i get a number ,that is ${Math.floor(Math.random() * 12)}`); } render() { return ( <div> {/* 注意 js属性名不能有-横杠,所以只能用中括号的形式替代,而不能用点号访问 */} <p className={child['sub-text']}> I am {this.stateChild.frstName}, my family is {this.props.lastName} </p> <button onClick={this.showMyself} style={{ marginRight: '1em' }}> 自我介绍 </button> <button onClick={() => this.tellMeSomething()}>自我介绍</button> </div> ); } }
这里有几个注意要点:
props
, 且类组件构造函数第一行必须调用父类构造函数super
,至于你是否需要将props
给super
,看你的需要,这个不强求;state
(为了使用setState
);但是组件接受的配置项props
名称是锁死的类组件的this绑定常用有两种方式:
bind
函数重新绑定方法的this (注意要重新赋值)this.showMyself = this.showMyself.bind(this);
render
函数时,使用箭头函数,使用父级作用域的this<button onClick={() => this.tellMeSomething()}>自我介绍</button>
如果不使用箭头函数或重新绑定,this
默认指向了undefined。
在非严格模式下调用者不是应该为Window对象吗?在这里,要知道一个知识点:ES6 的 class 语法,所有在 class 中声明的方法都会自动地使用严格模式。
当然这里还有其他方法,比如:
这种写法是ES7的写法,ES6并不支持,不过我们可以配置你的开发环境支持ES7。
handleClick = () => { console.log(this.state.name) }
<button onClick={this.addEvent.bind(this)}></button>
类似vue,react导入样式直接导入css文件; 样式名称写作className
而非class
, 其余类似html中一样使用
import './style/index.css' export default class Child extends React.Component { render() { return ( <div className='wrapper'> { /* ... */ } </div> ); } }
CSS模块化,也就是vue里面的<style scoped></style>
,让样式仅在当前组件生效。
css文件命名为[componentName].module.css
, componentName 为组件名称,也就是说css文件名和组件名部分一致且以.module.css
结尾。
import child from './child.module.css'; export default class Child extends React.Component { render() { return ( <div className={child['sub-text']}> { /* ... */ } </div> ); } }
React 绑定事件,类似Html原生,只是on后面一个字母大写 且绑定的 属性/函数 使用大括号而非引号。
官网比较二者差异如是说:
<button onclick="hello"></button> <button onClick={ hello }/> <button name={{ name: 123 }} />
另外,vue中使用v-bind:proper="value"
这种方式引用变量/属性,即双/单引号应用; 对应的React 使用{ }
括号引用变量/属性,若是临时对象,内部写临时对象,和vue一样。
React 采用jsx,让JavaScript和html混合(可将html视为特殊的字符串),因此使用JavaScript语法(if-else、Array.prototype.map)返回不同html结构来进行渲染。
let dom = null if(condition){ dom = <p>this is { condition }</p> }else{ dom = <p>this is empty</p> } return dom
注意React循环渲染,需要一个key字段标识元素唯一性,这点在vue里面也有。
// 类组件其他部分省略 render(){ return <div> { this.state.list.map((item,index)=><p key={index}>{ item.name }</p>) } </div> }
React 存在类似vue的生命周期
//类组件其他省略... // 组件挂载 类似Vue的mounted componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } // 组件将卸载 类似Vue的beforeDestroy componentWillUnmount() { clearInterval(this.timerID); }
更多详细的生命周期,见 旧版React官网-组件生命周期
React 中有两种组件风格,类式(class
)和函数式(Function
),将类组件转为函数组件如下:
render()
方法返回值作为函数组件的返回值, 其他内容视情况放在函数组件内部或外面;props
作为函数组件的入参,this.props
改为props
;二者最终效果一致,但是在还是存在很多不同:
后面由于函数组件的简洁性,后面统一使用函数组件做演示。
将上文的Child
类组件转为函数组件:
import React, { useState } from 'react'; // 注意这里React虽然没使用,但是定义函数组件还是要导入 import child from './child.module.css'; export default function childFunc(props) { const [firstName, setFirstName] = useState('Jerry'); const showMyself = () => { console.log(firstName + '-' + props.lastName); setFirstName('TONY'); }; const tellMeSomething = () => { console.log(`i get a number ,that is ${Math.floor(Math.random() * 12)}`); }; return ( <div> <p className={child['sub-text']}> I am {firstName}, my family is {props.lastName} </p> <button onClick={showMyself} style={{ marginRight: '1em' }}> 自我介绍 </button> <button onClick={tellMeSomething}>tell me something</button> </div> ); }
Hook 是一个特殊的函数,它可以让你钩入React 的特性(V16.8开始)。例如,useState
是允许你在 React 函数组件中添加 state(状态变量) 的 内置函数。
import React, { useState, useEffect } from 'react'; export default function HooksTest(props) { const [content, setContent] = useState('init content'); useEffect(() => { console.log('no parameters to useEffect'); }); useEffect(() => { console.log('a empty arry to useEffect'); }, []); useEffect(() => { console.log('a content variable to useEffect', content); }, [content]); const changeContent = () => { setContent(Math.floor(Math.random() * 10) + ':' + content); }; return <p onClick={changeContent}>{content}</p>; }
在 严格模式 下,React 会调用你的某些函数两次而不是一次:
function TodoList() { // 该函数组件会在每次渲染运行两次。 const [todos, setTodos] = useState(() => { // 该初始化函数在初始化期间会运行两次。 return createTodos(); }); function handleClick() { setTodos(prevTodos => { // 该更新函数在每次点击中都会运行两次 return [...prevTodos, createTodo()]; }); } // ... }
例如,这个不纯的更新函数改变了 state 中的一个数组:
setTodos(prevTodos => { // 错误:改变 state prevTodos.push(createTodo()); });
因为 React 调用了两次更新函数,所以你将看到 todo 被添加了两次,所以你将知道出现了错误。在这个例子中,你可以通过 替换数组而不是更改数组 来修复这个错误:
setTodos(prevTodos => { // 正确:使用新状态替换 return [...prevTodos, createTodo()]; });
现在,这个更新函数是纯粹的,所以多调用一次不会对行为产生影响。这就是为什么 React 调用它两次可以帮助你找到错误的原因。只有组件、初始化函数和更新函数需要是纯粹的。事件处理函数不需要是纯粹的,所以 React 不会两次调用你的事件处理函数。 这种 仅在开发环境下生效 的行为有助于 保持组件的纯粹性。React 使用其中一个调用的结果,而忽略另一个调用的结果。只要你的组件、初始化函数和更新函数是纯粹的,就不会影响你的逻辑。但是,如果它们意外地不纯粹,这将帮助你注意到错误。
由于React遵从Immutable的设计思想,永远不在原对象上修改属性而是产生新的对象。组件渲染(如组件的props或state改变)是自顶向下的进行递归更新的,也就是说,React 中假如 要渲染的组件 里还有十层嵌套子元素,那么所有层次都会递归的重新render(在不进行手动优化的情况下)。
Vue 的响应式更新精确到组件级别:vue每个组件都有自己的渲染 watcher,它掌管了当前组件的视图更新,但是并不会掌管子组件的更新(即不会深入到子组件内部进行更新)。
import { memo } from 'react'; const List = memo(function List({ items }) { // ... });
如果想在组件第一次渲染前延迟加载这个组件的代码,请替换成以下导入方式:
import { lazy } from 'react'; const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
此代码依赖于 动态 import(),可能需要你的打包工具或框架提供支持。
<Suspense>
允许你显示一个退路方案(fallback)直到它的子组件完成加载。import { Suspense } from 'react'; // React 将展示你的 退路方案fallback(Loading组件)直到子组件(SomeComponent)需要的所有代码和数据都加载完成。 <Suspense fallback={<Loading />}> <SomeComponent /> </Suspense>
const [state, setState] = useState(initialState);
useEffect()
组件状态变量变化、组件重新渲染时触发其回调参数。
// 返回值undefined useEffect(callback, dependencies?)
对于参数 dependencies:
useRef useRef 是一个 React Hook,它能让你引用一个不需要渲染的值。和useState区别在于,useState创建的变量变化会导致组件重新刷新, useRef创建的变量不会。
const ref = useRef(initialValue) ref.current = 'xxx'
useContext 函数组件使用/消费 Context,见下分组件通信。
useReducer
useReducer 是 React 中一种用于管理组件状态的钩子函数。它的作用是将组件的状态分解为多个值,并提供一种可预测、可控的状态更新方式,从而使得代码更加清晰易懂。
当state是一个变量时候,与useState
相比useReducer
管理大对象条例更清晰。
const [state, dispatch] = useReducer(reducerFunc, initialState);
例如
import React, { useReducer } from 'react'; // 定义 reducer 更新函数,用于控制状态的变化; 返回变化后的对象而不是修改原本的对象 function reducer(state, action) { switch (action.type) { case 'INCREMENT': // 当 action 的类型为 'INCREMENT' 时,状态加1 return state + 1; case 'DECREMENT': // 当 action 的类型为 'DECREMENT' 时,状态减1 return state - 1; case 'DECREMENTother': // 加入额外参数 return state - antion.amount default: // 默认情况下,返回原来的状态 return state; } } function Counter() { // 使用 useReducer 钩子函数来管理状态 const [count, dispatch] = useReducer(reducer, 0); return ( <div> {/* 在组件中渲染状态值 */} <p>Count: {count}</p> {/* 点击按钮时,使用 dispatch 函数来触发状态更新 */} <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button> <button onClick={() => dispatch({ type: 'DECREMENTother', amount: 5 })}>-5</button> </div> ); }
const cachedValue = useMemo(calculateValue, dependencies)
例如:
import { useMemo } from 'react'; function TodoList({ todos, tab }) { const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] ); // ... return ( <div className={theme}> {/* ... 所以List的props一样就会跳过重新渲染 */} <List items={visibleTodos} /> </div> ); }
const cachedFn = useCallback(fn, dependencies)
在组件顶层调用 useCallback 以便在多次渲染中缓存函数:
import { useCallback } from 'react'; export default function ProductPage({ productId, referrer, theme }) { // 每当 theme 改变时,生成的函数不变 const handleSubmit = useCallback((orderDetails) => { post('/product/' + productId + '/buy', { referrer, orderDetails, }); }, [productId, referrer]); return ( <div className={theme}> <ShippingForm onSubmit={handleSubmit} /> </div> ); } // 子组件 import { memo } from 'react'; const ShippingForm = memo(function ShippingForm({ onSubmit }) { // ... });
默认情况下,当一个组件重新渲染时(state或props变化), React 将递归渲染它的所有子组件,因此每当因 theme 更改时而 ProductPage 组件重新渲染时,ShippingForm 组件也会重新渲染。 你可以将 ShippingForm 组件包裹在 memo 中。如果 props 和上一次渲染时相同,那么 ShippingForm 组件将跳过重新渲染。
自定义Hook必须以use
开头(语义上要求,你写其他的也能运行),相对于组件返回值为jsx,Hook返回值为状态值。
自定义HOOK:判断是否为长文本
import { useState, useEffect } from 'react'; // 这里可以不导入默认的React export default function TseMyHook(textVal) { const [isLongText, setIsLangText] = useState(false); useEffect(() => { setIsLangText(textVal.length > 5 ? true : false); }); return isLongText; // 这里也可以把原来的设置变量值方法一起返回,和useState一致 }
使用自定义HOOK:
import React, { useState, useEffect } from 'react'; import TseMyHook from './myHooks.jsx'; export default function HooksTest(props) { const [content, setContent] = useState('init'); const isLongStatus = TseMyHook(content); // ... const changeContent = () => { setContent(Math.floor(Math.random() * 10) + ':' + content); }; return ( <p onClick={changeContent}> {content} isLong: {String(isLongStatus)} </p> ); }
可以发现 HOOK就是(根据入参)返回 状态/函数 的函数,但是相对于一般函数HOOK能在输入值变化时自动变化而不用像一般函数一样手动调用(例如上文你将此功能封装成一般的isLongText
函数,但是入参content
变化时,你需要手动再次调用一次isLongText
函数并重新赋值)
HOOK 作用是在一定程度上减少代码耦合读,提取公共部分 代码或逻辑;
因此上述代码中 和将以下代码引入HookTest
组件中效果是一样的:
const [isLongStatus, setIsLongStatus] = useState(false); useEffect(() => { setIsLongStatus(textVal.length > 5 ? true : false); },[content]);
副作用和纯函数是js函数编程的概念,拥有以下两个特征的函数称为纯函数:
如:
function pureFunction(x){ return x*10; }
如根据函数参数不能确定唯一返回值或进行了无关返回值的操作,那么这个影响其不是纯函数操作就成为副作用。 如下面中arr删除元素的操作就是副作用,因为函数元素不仅和索引有关,更和数组本身有关。
const arr = [1,2,3,4] function unpureFunction(index){ var element = arr[index] arr.splice(index,1) return element }
或者进行了额外工作:
function pureFunction(x){ console.log(111); //副作用 return x*10; }
因为React函数组件的出现,函数组件返回jsx(即DOM内容),也产生了纯函数和副作用的问题。 由于React函数组件返回的是JSX,所以在React中副作用可以进一步明确为:
以非参数方式 影响React函数组件返回的jsx 的操作或元素,称为 React副作用(简称副作用)
当然也有人说:在 React 中,副作用指的是与组件渲染结果无关的任何操作(不是很认同)。
常见的React副作用有:
父组件向子组件通信,可以通过 props 方式传递数据;也可以通过 ref 方式传递数据;
ref 可以挂载到组件上也可以挂载到dom元素上,但是注意两点:
React.createRef()/useRef
方法生成一个ref( useRef
仅能用在函数组件,createRef
仅能用在类组件。 )。//父组件 class Parent extends Component{ constructor(){ super(); //通过 createRef() 生成ref this.childComp=createRef() } clickHandle=()=>{ //调用子组件的方法,并传递数据 this.childComp.current.childClickHandle("beijing"); } render(){ return ( <div> <button onClick={this.clickHandle}>按钮</button> //给子组件设置ref属性 <Child ref={this.childComp}></Child> </div> ) } }
子组件向父组件通信,通过回调函数方式传递数据。
//子组件 class Child extends Component{ state={ name:"admin", age:18 } childClickHandle=()=>{ this.props.showInfo({address:"beijing"}) } render(){ return ( <div> // 方式一:直接调用父组件的方法 <button onClick={this.props.showInfo.bind(this,this.state)}>按钮</button> //方式二:先调用自身的方法,再调用父组件的方法 <button onClick={this.childClickHandle}>按钮</button> </div> ) } } //父组件 class Parent extends Component{ clickHandle(data){ //data为子组件中传递过来的数据 //{address: "beijing"} //{name: "admin", age: 18, sex: "woman"} console.log(data); } render(){ return <Child showInfo={this.clickHandle.bind(this)}></Child> } }
其实 vue里面emit事件先父组件触发事件, 本质也是触发 父组件通过props发过来的函数。
这点有点像Vue的Provide/inject, Context/Consumer
将属性值从根组件导入到下面组件。
import React, { Component ,createContext} from 'react'; import ReactDOM from 'react-dom'; // cityContext.js文件, 定义Context //使用createContext()创建一个context,参数为默认值 const cityContext = createContext("beijing"); cityContext.displayName = "cityContextName"; //devtools中调试用 export default cityContext // parent.jsx,引入context并使用这个context的Provider主任 import cityContext from './cityContext' class Parent extends Component{ render() { return ( //使用Provider 将当前context的值传递给下面的组件树 <cityContext.Provider value="shenzhen"> <Child /> </cityContext.Provider> ) } } //Child.jsx 中间的组件不需要对数据进行传递 class Child extends Component{ render() { return <Grandson /> } } // Grandson.jsx 使用的地方导入context import cityContext from './cityContext' class Grandson extends Component{ //指定contextType 读取当前的context //react 会往上找到最近的 Provider,然后使用它的值 static contextType = cityContext; render() { return <div>{this.context}</div> //最终页面上输出 shenzhen } }
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; //导入需要使用的context对象 import CityContext from './CityContext'; import WeatherContext from './WeatherContext'; class Child extends Component{ render() { return ( <CityContext.Consumer> //子元素是函数组件的语法 { (prop)=>{ //参数是当前context对象里的数据 return ( <div> name:{prop.name},location:{prop.location} <WeatherContext.Consumer> //可以嵌套使用,也可以同级使用 { ({status})=>{ return <div>weather:{status}</div> } } </WeatherContext.Consumer> </div> ) } } </CityContext.Consumer> ) } }
函数组件可以使用 useContext
这个HOOK消费/获取Context的值。
// 方式1:userContext function ThemeLink1 (props) { // const theme = this.context // 会报错。函数式组件没有实例,即没有 this const theme = useContext(ThemeContext); return <p>link's theme333333 is {theme}</p> } // 方式二:Consumer function ThemeLink1 (props) { // const theme = this.context // 会报错。函数式组件没有实例,即没有 this return <ThemeContext.Consumer> { value => <p>link's theme is {value}</p> } </ThemeContext.Consumer> }
建立公有的父组件,通过中介的父组件传值。
react官方建议我们使用一个特殊的props (children)来将这些无法提前预知内容的子组件传递到渲染到结果中。
在react中没有插槽的概念但有一个也有相同的功能叫做组合模式,但是我们可以通过组合的形式实现‘插槽’;jsx中的所有内容都会通过children
这个prop属性传递到子组件中,使用react组合的方式可以实现类似于Vue插槽的功能。
function MainCom(props) { return <div> {props.children} </div> } function App() { return <MainCom> <h1>设置标题</h1> <h2>小标题</h2> <h2>大标题</h2> </MainCom> } // 多个子节点; children变为数组 function App1() { return <AppDiv> <MainCom> <div>张三</div> <div>李四</div> <div>王五</div> <div>赵六</div> </MainCom> </AppDiv> } function MainCom1(props) { return <div> {props.children?.map(children => { console.log(children) return children })} </div> }
定义具名插槽时与默认插槽时一样的只有使用的方式不同,使用的方式类似于传递一个属性.
function MainCom(props: Props) { return <div> {props.children} <div> <p>left</p> {props?.left} </div> <div> <p>right</p> {props.right} </div> </div> } function App() { return <AppDiv> <MainCom left={ <div> 左边组件 </div> } right={ <div>右边组件</div> } > <h1>设置标题</h1> </MainCom> </AppDiv> }
传递的props.children
不再是jsx
,而是返回接受参数并返回jsx的函数。
在使用props.chilren
时调用函数并传入参数,返回对应的jsx即可。
function Container(props){ return ( <> { props.children('传递给子组件的数据') } </> ) } function App(){ return ( <> <Container> {/* 通过传递函数实现作用域插槽 */} { v =>(<div>{v}</div>) } </Container> </> ) }
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
如两个展示组件,A组件展示摄氏度数值,B组件展示华氏温度,则可以提将两者的温度提取到最近的父组件中,在通过props
传递给他们。
在 React 应用中,任何可变数据应当只有一个相对应的唯一 “数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
我们推荐使用一个特殊的children
prop(类似vue的插槽),进行组件之间的组合。
如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。
React不推荐使用继承(class方式的extends白白浪费了)。
React哲学公式:
页面UI = Fn(状态);
为了做到以上,可以将代码书写和思考过程分为以下几个:
<input>
、 <textarea>
和 <select>
)通常自己维护 state,并根据用户输入进行更新。而在React中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。我们可以把两者结合起来,使 React的state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”(简而言之,就是通过state控制组件)。
Ref
获取节点的引用这种方式控制组件:class FileInput extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); this.fileInput = React.createRef(); } handleSubmit(event) { event.preventDefault(); // ref通过current属性访问 alert( `Selected file - ${this.fileInput.current.files[0].name}` ); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Upload file: <input type="file" ref={this.fileInput} /> </label> <br /> <button type="submit">Submit</button> </form> ); } } const root = ReactDOM.createRoot( document.getElementById('root') ); root.render(<FileInput />);
使用Ref控制组件, 函数组件则调用useRef
函数。
class CustomTextInput extends React.Component { constructor(props) { super(props); // 创建一个 ref 来存储 textInput 的 DOM 元素 this.textInput = React.createRef(); this.focusTextInput = this.focusTextInput.bind(this); } focusTextInput() { // 直接使用原生 API 使 text 输入框获得焦点 // 注意:我们通过 "current" 来访问 DOM 节点 this.textInput.current.focus(); } render() { // 告诉 React 我们想把 <input> ref 关联到 // 构造器里创建的 `textInput` 上 return ( <div> <input type="text" ref={this.textInput} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。
不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。
下面的例子描述了一个通用的范例:使用 ref 回调函数,在实例的属性中存储对 DOM 节点的引用。
class CustomTextInput extends React.Component { constructor(props) { super(props); this.textInput = null; this.setTextInputRef = element => { this.textInput = element; }; this.focusTextInput = () => { // 使用原生 DOM API 使 text 输入框获得焦点 if (this.textInput) this.textInput.focus(); }; } componentDidMount() { // 组件挂载后,让文本框自动获得焦点 this.focusTextInput(); } render() { // 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React实例上(比如 this.textInput) return ( <div> <input type="text" ref={this.setTextInputRef} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
你可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。
function CustomTextInput(props) { return ( <div> { /* 设置Ref回调 */ } <input ref={props.inputRef} /> </div> ); } class Parent extends React.Component { render() { return ( { /* 把Ref在父组件中存起来 */ } <CustomTextInput inputRef={el => this.inputElement = el } /> ); } }
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
例如,可以使用这个来进行移动端和PC端适配,如果是移动端就返回<Mobile_component/>
组件,否则就返回<PC_component/>
组件。
const getDeviceComponent()=>{ //... } export default function App(){ const component = getDeviceComponent(<PC_component/>, <Mobile_component/>); return component; }
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案(类似vue3的teleport方案)。
ReactDOM.createPortal(child, container);
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:
render() { // React 挂载了一个新的 div,并且把子元素渲染其中 return ( <div> {this.props.children} </div> ); }
然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框
render() { // React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。 // `domNode` 是一个可以在任何位置的有效 DOM 节点。 return ReactDOM.createPortal( this.props.children, domNode ); }
尽管 portal 可以被放置在 DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。由于 portal 仍存在于 React 树, 且与 DOM 树 中的位置无关,那么无论其子节点是否是 portal,像 context 这样的功能特性都是不变的。(vue3的Teleport也是这样)
这包含事件冒泡。一个从 portal 内部触发的事件会一直冒泡至包含 React 树的祖先,即便这些元素并不是 DOM 树 中的祖先。
合成事件就是React的对原生事件再次封装。
SyntheticEvent 事件包装器实例将被传递给你的事件处理函数,它是浏览器的原生事件的跨浏览器包装器。除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault()。
如果因为某些原因,当你需要使用浏览器的底层事件时,只需要使用 nativeEvent 属性来获取即可。合成事件与浏览器的原生事件不同,也不会直接映射到原生事件。
基本思想MVVM一样,但是实现思路是不一样:
由于React遵从Immutable的设计思想,永远不在原对象上修改属性而是产生新的对象。组件渲染(如组件的props或state改变)是自顶向下的进行递归更新的,也就是说,React 中假如 要渲染的组件 里还有十层嵌套子元素,那么所有层次都会递归的重新render(在不进行手动优化的情况下)。React元素是不可变的。创建元素后,不能更改其子元素或属性。元素就像电影中的单个帧:它表示某个时间点的UI。
Vue 的响应式更新精确到组件级别:vue每个组件都有自己的渲染 watcher,它掌管了当前组件的视图更新,但是并不会掌管子组件的更新(即不会深入到子组件内部进行更新)。
很多人说React适用于大型系统,而Vue适用于中小型系统。 我觉得,伪命题:并没有React比Vue更适合大型应用之说,只要用得好,Vue也可以开发大型项目(人不行别怪路不平), 但是我得承认,React上手难度和包含的思想,确实比vue难一些,但是从来就没有说难就是好,其实更多是人云亦云。
引用知乎大佬一句话:
首先上结论:项目维护的难易度,大型项目维护的难易度,与框架无关,与语言无关,与编程范式也无关,只与人有关。任何时代,任何框架都存在过容易维护的项目和大量不容易维护的项目,重要的是哪拨人写的,牛逼点的人制定了严格的代码规范,项目内的人执行力也强,都很好的遵守了一套标准,并且这拨人也都明白什么是好的标准,项目自然就很好维护,风格统一思想统一嘛。难就难在这个项目经历了无数人的手,并且这些人水平参差不齐,又没有严格的代码规范,因此越到后面维护的人越想骂娘了,特么的写的都是屎,维护不下去了,而这样的项目在现实业务中不要太多啊!所以与其讨论框架的优越性,不如努力提升自己的能力,知道什么是好,怎么写好,不被接手你代码的人喷你才是正事啊
<script> export default { render: function (createElement) { return createElement( 'h' + this.level, // 标签名称 this.$slots.default // 子节点数组 ) }, props: { level: { type: Number, required: true } } } </script>
使用组件(添加jsx依赖)
import AnchoredHeading from './AnchoredHeading.vue' new Vue({ el: '#demo', render: function (h) { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) } })
提供了在定义 Vue 组件时提供类型推导的辅助函数。如defineComponent()
、defineAsyncComponent()
,后者为懒加载组件:
import { ref, h } from 'vue' export default const Comp = defineComponent( (props) => { // 就像在 <script setup> 中一样使用组合式 API const count = ref(0) return () => { // 渲染函数或 JSX, 注意这里需要访问value并减少一层大括号 return <div>{ count.value }</div> } }, // 其他选项,例如声明 props 和 emits。 { props: { /* ... */ } } );
文章目录