-
你们用哪个UI库开发react
-
摘要 本文总结了 ReactNative 开发中常见问题及解决方法。从 ScrollView 在 TouchableOpacity 组件内滑动困难到 Xcode 编译路径设置,都有相应解决方案。此外,还介绍了热更新问题、高度获取、强制横屏UI适配、清理缓存等实用技巧。 引言 ReactNative 作为一种跨平台开发框架,尽管强大,但也常伴随着一些问题。本文收集并解答了一些常见问题,为开发者提供了一些实用的技术指南。 正文 ScrollView内无法滑动 在 TouchableOpacity 组件内使用 ScrollView 可能导致滑动失效。解决方法是将 ScrollView 内容用 TouchableOpacity 包裹,并设置 onPress={() => {}} 属性。 <TouchableOpacity onPress={() => {}}> <ScrollView> {/* Scrollable content */} </ScrollView> </TouchableOpacity> RN热更新中的文件引用问题 使用 codepush 进行热更新后,在 Android 系统中 src 目录下的音频文件可能无法引用。解决方法是将文件放到原生系统中,因为热更的 bundle 文件无法包含音频文件。 RN中获取高度的技巧 获取屏幕高度和窗口高度的不同方法: // 屏幕高度(状态栏+安全区+下方虚拟按键操作区) Dimensions.get('screen').height // 窗口高度(状态栏+安全区) Dimensions.get('window').height RN强制横屏UI适配问题 横屏下获取的宽、高不同于竖屏状态下的尺寸。通常,可以采用横屏下宽度大于高度的普遍规则进行页面适配。 低版本RN(0.63以下)适配iOS14图片无法显示问题 修改 RCTUIImageViewAnimates.m 文件,添加以下代码片段,确保 iOS14 以上系统可以正常显示图片: if (_currentFrame) { //275行 layer.contentsScale = self.animatedImageScale; layer.contents = (__bridge id)_currentFrame.CGImage; } else { //加上这个 不然ios14以上的系统看不见图片 [super displayLayer:layer]; } RN清理缓存 清理缓存的步骤: watchman watch-del-all rm -rf node_modules && npm install rm -rf /tmp/metro-bundler-cache-* (npm start --reset-cache / react-native start --reset-cache) rm -rf /tmp/haste-map-react-native-packager-* RN navigation参数取值 获取导航参数的方法: console.log(this.props.navigation.state.params.data) pod install 或者npm install 443问题处理 解决 443 错误的步骤: 修改 /etc/hosts,添加: 199.232.68.133 raw.githubusercontent.com 140.82.113.3 github.com 清空 git 代理: git config --global --unset http.proxy git config --global --unset https.proxy git config --global --list 设置环境变量: env GIT_SSL_NO_VERIFY=true 打开要处理的IPA文件 第一项,填写我们需要重签名的 ipa 路径(当前导入的路径跟导出的路径) 设置签名使用的证书和描述文件 测试配置阶段使用开发测试证书,方便安装到手机测试混淆后ipa是否工作正常,测试ok,最后准备上架的时候再改成发布证书和发布描述文件 如果ipa需要特殊的权限配置,可以使用权限配置文件 如果希望直接处理完后安装到设备,则勾选安装到设备选项 苹果手机数据线连接电脑即可识别设备,如果链接成功后没显示设备,则先安装itunes或者ios驱动。 开始ios ipa重签名 第四项点击开始处理,ipaguard会自动尝试讲ipa安装到手机,如果是发布证书并且忘记关闭安装到设备选项,则安装可能会失败,但是ipa是正常生成的,可以用来上架。 总结 ReactNative 开发中会遇到各种问题,但通过本文提供的方法和技巧,可以有效解决大部分常见问题。除了以上列举的问题外,还有诸如 Xcode 路径配置、iOS 下载链接拼接等问题都有相应的解决方案。 ———————————————— 版权声明:本文为CSDN博主「劝君更尽一杯酒1」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2301_79527119/article/details/135412514
-
React专注View层,组件是React的核心原理之一,一个完成的路由应用层是有一个个独立组件拼装而成的。组件亦是react的基础的一部分。组件的分类高阶组件与渲染属性受控组件和非受控组件托管组件和非托管组件无状态组件和有状态组件展示型组件和容器组件高阶组件与渲染属性高阶组件(HOC) 是react中用于重用组件逻辑的高级技术。HOC本身不是ReactAPI的一部分,他们是从react构思本质中浮现出来的一种模式。具体来说,高阶组件是一个函数,能够接受一个组件并返回一个新组价export default function showMouse(WrapperComponent) { return class extends React.Component { constructor(props) { super(props) this.state = { x: 0, y: 0, } } handleMouseMove(e) { this.setState({ x: e.clientX, y: e.clientY, }) } render() { const newProps = { tag: 'handMouse' } return ( <div onMouseMove={this.handleMouseMove.bind(this)}> <WrapperComponent {...this.props} {...newProps} mouse={this.state} /> </div> ) } }}//使用import showMouse from './pages/personal/showMouse/index'class App extends React.Component { constructor(props) { super(props) } render() { const { mouse } = this.props return ( <div className="App"> <p>X:{mouse.x}</p> <p>Y:{mouse.y}</p> </div> ) }}export default showMouse(App)渲染属性(Render Props) 术语Render Props 是指一种技术,用于使用一个值为函数的prop在react组件之间的代码共享拿上边例子,假设,有一个需求要在鼠标移动时,先界面上显示鼠标坐标,但由于此功能要在多个组件, 多个页面中使用。意味着界面显示数据是固定的,但UI组件显示方式会发生变化。在前端开发中,UI是高频变化的,业务逻辑与业务数据反而是相对稳定,这种将展示数据交由调用者来控制的方式,极大提高了UI的部分高扩展性。 提示:该组件的官名是Render Props,但不一定要通过一个render.props来实现。该组件的核心思想是:由调用者决定如何显示。所以另一种写法如下:import React, { Component } from 'react'export default class Mouser extends Component { constructor(props) { super(props) this.state = { x: 0, y: 0, } } handleMouseMove(e) { this.setState({ x: e.clientX, y: e.clientY, }) } render() { return ( <div style={{ height: '200px' }} onMouseMove={this.handleMouseMove.bind(this)} > {this.props.children(this.state)} </div> ) }}//使用render() { return ( <div className="App"> <Mouser> {(mouse) => ( <div> <p>X:{mouse.x}</p> <p>Y:{mouse.y}</p> </div> )} </Mouser> )}React中的this.props.children API也是个函数,因此,此例子中使用this.props.children方法,同样将UI的展示方式交由了调用者决定。高阶组价和渲染属性的特点高阶组件特点接受一个组件,返回一个组件代理props,上述例子中组件的props完全由组件控制低侵入性。showMouse函数组件赋予mouse数据,并没有影响外部组件内部的逻辑拥有反向继承的能力渲染组件特点本身不决定数据展示方式,将数据展示方式交由调用者决定无侵入性。上述高阶组件例子中,由于组件被赋予props.mouse。因此组件在调用时不能在拥有相同的props.mouse,因此说高阶组件是低入侵性,而不是无侵入性。但渲染属性是已回调函数的方式,决定其不会有任何侵入性。共同点均有解决逻辑服用的作用均通过逻辑分离、组件拆分、组合调用的方式,实现代码复用,逻辑复用区别:高阶组件通过柯里化函数实现,渲染属性通过回调函数实现高阶组件通过函数调用、装饰器调用。渲染属性通过回调函数重写实现受控组件和非受控组件受控组件和非受控组件是针对表单元素而言 受控组价:在HTML中,像text、textarea、select这类表单元素会维持自身状态,并根据用户输入进行更新,但在react中,可变的状态通常保存在组件的状态属性state中,并且只能通过setState方法更新,相应的,react控制的输入表单元素称为“受控组件”import React, { Component } from 'react'export default class AccessControl extends Component { state = { userName: '张三', } handleChange = (e) => { this.setState({ userName: e.target.value, }) } render() { const { userName: name } = this.state return ( <div> 用户名: <input type="text" value={name} onChange={this.handleChange} /> </div> ) }}其书写方式和普通的react组件书写区别不大此类组件特指表单元素表单元素数据依赖状态受控组件的修改必须使用onchange事件绑定实时映射状态值非受控组件与受控组件相反,非受控组件不受状态的控制,通过virtual Dom来获取数据import React, { Component } from 'react'export default class noControls extends Component { constructor(props) { super(props) this.handleSubmit = this.handleSubmit.bind(this) this.input = React.createRef() } handleSubmit(e) { console.log('the value', this.input.value) e.preventDefault() } render() { return ( <form> <input type="text" name="names" ref={(input) => (this.input = input)} /> <input type="submit" value="submit" onClick={this.handleSubmit} /> </form> ) }}特指表单元素表单数据存储在DOM中通过virtual dom的方式获取表单数据托管组件和非托管组件托管组件:将数据委托其他组件控制,下面例子中的inputLeft与inputRight组件本身发不处理用户输入的信息,直接托管到父组件Hosting中,在没有Mobx和redux等状态管理react项目中,通常采用状态提升,把所有数据委托给一个相同的父组件,由父组件控制数据与逻辑,把这种数据托管给其他组件控制的组件,称之为托管组件import React, { Component } from 'react'class InputLeft extends Component { constructor(props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange(e) { const { onChange } = this.props onChange && onChange(e.target.value) } render() { return ( <input type="text" name="left" value={this.props.value} onChange={this.handleChange} /> ) }}class InputRight extends Component { constructor(props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange(e) { const { onChange } = this.props onChange && onChange(e.target.value) } render() { return ( <input type="text" name="right" value={this.props.value2} onChange={this.handleChange} /> ) }}export default class Hosting extends Component { constructor(props) { super(props) this.state = { value: '', value2: '', } this.handleChange = this.handleChange.bind(this) } handleChange(value) { this.setState({ value: value + '', }) console.log('value is', this.state.value) } render() { const { value } = this.state const { value2 } = this.state return ( <div> LEFT: <InputLeft value={value} onChange={this.handleChange}></InputLeft> RIGHT: <InputRight value={value2} onChange={this.handleChange}></InputRight> </div> ) }}非托管组件:及组件通过拥有状态处理自身的数据import React ,{Component} from 'react'export default class BaseInput extends Component{ constructor(){ this.satet = { name:'' } } handleChange(e){ const name = e.target.value; this.setState({name}) } render(){ return( <input type="text" value={this.state.name} onChange={this.handleChange.bind(this)} /> ) }}展示型组件和容器组件容器型组件:主要是组件是如何工作的,更具体的就是数据是怎么更新的,不包含任何的virtual dom(虚拟dom)的修改和组合,只关心数据的更新,不在乎dom是什么样的import React from 'react'import ReactDom from 'react-dom'import RootTable from './RootTable' //引入展示型组件class RootSearch extends React.Component{ constructor(props){ this.state = {id:'',name:'',nikeName:''} } componentDidMount(){ this.axios.get('/user',{params:{id:2123}}).then((res)=>{ this.setState({ id:res.data.id, name:res.data.name, nikeName:res.data.nikeName }) }) } render( <div> <RootTable id={this.state.id} name={this.state.name} /> </div> )}展示型组件:其主要的就是组件要怎么渲染,对virtual Dom具体是怎么操作的,也包含样式的修改组合,同时,它不依赖任何形式的storeconst UIcomponent = (props) =( <div> {props.dataSource.map(callbackfn:item=>( <p onClick={()=>props.onClick(item)} >item.userName</p> ))} </div>)对此,展示型组件只考虑两个事情:输入,输出无状态组件和有状态组件无状态组件:就是没有私有状态的组件,没有state无状态组件目前有两种写法://无状态 类组件 stateless class component(scc)class sccComponenet extend React.Componenet{ render(){ return( <div>用户名: <input type="text" value={this.props.value} onChange={this.props.onChange} /> </div> ) }}//无状态 函数组件 stateless Function Component(SFC)const SFCcomponent = (props)=>{ <div>用户名: <input type="text" value={props.value} onChange={props.onChange} /> </div>}export default SFCcomponent这两种写法的区别:在SCC中仍然可以访问react的生命周期,如componentDidMount等在SFC中则无法访问所以SFC在渲染上高于SCC状态组件:带有state用以处理业务逻辑、UI逻辑、数据处理等。配合react的生命周期函数,用以在特殊时刻控制状态class StatefulComponent extends Component{ constructor(props){ super(props) this.state ={//定义状态} } componentDidMount(){//do something}}
-
虚拟滚动: 复用你的DOM元素,根据用户的滚动方向移除你视口的一个元素。当列表需要展示上千上万级别甚至是趋向于无限的数据时,DOM元素的堆积会导致浏览器渲染的性能降低,导致用户体验降低甚至出现页面假死状态。这个时候使用虚拟滚动的技术,可以让DOM数量维持在固定的数量,从而解决以上问题。但对于少量数据的场景下,使用传统的加载方式会优于虚拟滚动,虚拟滚动的核心是监听滚动事件去进行复杂的逻辑计算,两者衡量之下,少量的数据的DOM消耗要远小于虚拟滚动的计算已有虚拟滚动技术的应用: Google Music, Twitter, Facebook。文章接下来会讲述 固定高度的虚拟滚动 到 动态高度的虚拟滚动是如何实现,技术的难点是什么,并以React的框架为基础,用代码的形式去介绍算法核心点。源码可以参考此处固定高度为了实现跟传统列表加载的体验一样,虚拟滚动需要做到以下几点: 计算容器能承载的DOM元素容量 模拟滚动高度 实时计算显示的元素1. 承载容量在计算机领域中有一个名词叫视口,代表当前可见的计算机图形区域。 在虚拟滚动技术中,视口是承载DOM元素的容器,超出该容器的DOM元素是不可见的,需要通过滚动来展示,如下图,白色部分的高度就是容器的高度,蓝色的DOM元素就是用户可见的元素。假设容器高度为H, 单个DOM元素的高度为DH,那么容器可见的数量为 VISIBLE_COUNT = H / DH。但在实际滚动过程中,仅仅渲染可见的数量是不够的,因为滚动过程的实时计算会导致浏览器的渲染不够及时,可能会出现留白的情况,所以我们需要在上下两边都加上一个缓存用的DOM元素。 假设BUFFER_SIZE是3, 按照上图所示,则真正需要渲染的DOM的数量是 4 + 3 * 2 = 10个, ITEM_COUNT = ⌈H / DH⌉ + BUFFER_SIZE * 2;为了更方便去过滤该渲染的DOM元素,那么我们设置两个变量去筛选,firstItem和lastItem。 useLayoutEffect(() => { ELEMENT_HEIGHT = outerHeight(itemRef.current); const containerHeight = containerRef.current?.clientHeight ?? 0; VISIBLE_COUNT = Math.ceil(containerHeight / ELEMENT_HEIGHT); setLastItem(VISIBLE_COUNT + BUFFER_SIZE); }, [])2. 模拟滚动高度因为要让用户感受跟传统滚动一样的效果和体验,然而有限的DOM元素不足以撑开上千个数据的列表,所以我们要用css去帮忙撑开,撑开的方式有两种: 通过设置容器的padding-bottom,让其出现滚动条 设置一个哨兵元素,并设置哨兵元素的translateY的值,同样能使容器出现滚动条。 💡PS:哨兵在现实中,是用来解决国家之间的边界问题,不直接参与生产活动。在计算机领域也是为了处理边界的问题,可以减少很多边界问题的判断,降低代码复杂性。我这边推荐第二种方式,理由是:浏览器的重排与重绘。特别是后面介绍动态高度滚动的时候,会不断计算可滚动的高度,这对于性能来说也是一些优化,既然能优化,当然要优化得彻底一些。因为每个DOM元素的高度是固定的,所以只要每次列表有变化的时候,再对这个高度进行计算就可以,scrollHeight = list.length * ELEMENT_HEIGHT; <div onScroll={scroll} ref={containerRef} className={styles.container}> // 哨兵,用于撑开滚动高度 <div className={styles.sentry} style={{ transform: `translateY(${scrollHeight}px)` }} ></div> // 可以先忽略下方的代码 { visibleList.map((item, idx) => <div key={idx} style={{transform: `translateY(${item.scrollY}px)`}} className={styles.wrapItem} > <Item ref={itemRef} item={item} /> </div> ) } </div> useLayoutEffect(() => { // 可先忽略这段代码 list.forEach((item, idx) => { item.scrollY = idx * ELEMENT_HEIGHT; }) // list变化时,更新scrollHeight的值 setScrollHeight(list.length * ELEMENT_HEIGHT); }, [list]);3. 实时计算显示的元素如何正确显示当前已滚动高度所对应的元素呢?这是本节所要解决的问题,也是整个虚拟滚动技术(固定或动态)的核心。在固定高度的情况下,其实在加载list的时候,已经确定每个元素的位置,只要像哨兵一样,设置translateY的值就可以了。同时也设置了一个visibleList变量来过滤需要渲染的DOM元素,这个变量依赖上方所提到的firstItem,lastItem以及list。 useLayoutEffect(() => { // list变化时,为每个item设置scrollY list.forEach((item, idx) => { item.scrollY = idx * ELEMENT_HEIGHT; }) // list变化时,更新scrollHeight的值 setScrollHeight(list.length * ELEMENT_HEIGHT); }, [list]); useLayoutEffect(() => { setVisibleList(list.slice(firstItem, lastItem)); }, [list, firstItem, lastItem]); <div onScroll={scroll} ref={containerRef} className={styles.container}> // 哨兵,用于撑开滚动高度 <div className={styles.sentry} style={{ transform: `translateY(${scrollHeight}px)` }} ></div> // 显示的时候,设置translateY的值 { visibleList.map((item, idx) => <div key={idx} style={{transform: `translateY(${item.scrollY}px)`}} className={styles.wrapItem} > <Item ref={itemRef} item={item} /> </div> ) } </div>到这里,万事俱备只欠滚动事件的处理。在滚动处理中,我们需要根据容器的scrollTop来计算当前应该展示哪些数据,因为整个列表的渲染都依赖与visibleList的变量,而visbleList依赖于firstItem和lastItem,而lastItem又依赖于firstItem,所以滚动仅需要计算firstItem。正常情况下,大部分人都简单想到,既然元素是固定高度的,那直接用⌊scrollTop / ELEMENT_HEIGHT⌋就可以得出firstItem了。确实! 这是一个最简单且有效的办法。但为了让各位读者有对下面的动态高度技术有循序渐进的感觉,这里延伸出一个新的概念:【锚点元素】。这个锚点有两个属性index和offset,指向的是第一个可视元素的index,offset表示滚动高度超过这个元素的值,如果offset > ELMENT_HEIGHT的时候,则index++。 const updateAnchorItem = useCallback( (container) => { const index = Math.floor(container.scrollTop / ELEMENT_HEIGHT); const offset = container.scrollTop - ELEMENT_HEIGHT * index; anchorItem.current = { index, offset } }, [], ) const scroll = useCallback( (event) => { const container = event.target; // const tempFirst = Math.floor(container.scrollTop / ELEMENT_HEIGHT); // setFirstItem(tempFirst); // 下面搞那么多花里胡哨,都不如上方来得简单 const delta = container.scrollTop - lastScrollTop.current; lastScrollTop.current = container.scrollTop; const isPositive = delta >= 0; anchorItem.current.offset += delta; let tempFirst = firstItem; if (isPositive) { // 向下滚 if (anchorItem.current.offset >= ELEMENT_HEIGHT) { updateAnchorItem(container); } // 更新完的index是否发生了变化 if (anchorItem.current.index - tempFirst >= BUFFER_SIZE) { tempFirst = Math.min(list.length - VISIBLE_COUNT, anchorItem.current.index - BUFFER_SIZE) setFirstItem(tempFirst); } } else { // 向上滚 if (container.scrollTop <= 0) { anchorItem.current = { index:0, offset: 0 }; } else if (anchorItem.current.offset < 0) { updateAnchorItem(container); } // 更新完的index是否发生了变化 if (anchorItem.current.index - firstItem < BUFFER_SIZE) { tempFirst = Math.max(0, anchorItem.current.index - BUFFER_SIZE) setFirstItem(tempFirst); } } setLastItem(Math.min(tempFirst + VISIBLE_COUNT + BUFFER_SIZE * 2, list.length)); // 拉到底层,加载新的数据 if (container.scrollTop + container.clientHeight >= container.scrollHeight - 10) { setList([...list, ...generateItems()]); } }, [list, updateAnchorItem, firstItem], )至此,固定高度的实现已经完成。点击此处跳转到demo动态高度动态高度,顾名思义,高度是不固定的,只有渲染完成的时候才知道这个DOM元素有多高,那么带给我们的难度会有: 需要知道何时元素渲染完,然后获取高度 模拟滚动高度不确定,需要实时计算 每个可视元素的translateY值不固定 当元素高度调整完后,对滚动条的影响如何我们将对上面的点一个一个解决。虽然具体的思想跟固定高度的实现差不多,但对于细节的一些把控,还是需要细细斟酌的。1. 如何知道何时元素渲染完ECMA已经提供了一个对象给我们使用 — ResizeObserver,作用就是监听观察的DOM的边界框发生的变化。兼容性可以看下图,不过可以使用Mutationobserver去实现polyfill,Github已经有大神去实现了,polyfill地址。通过polyfill我们就可以安心使用这个对象达到目的。(除非要兼容IE,既然是IE,就甭想着用虚拟高度技术了!!!) const resizeObserver = useRef<ResizeObserver>( new ResizeObserver((entries, observer) => { sizeChange(); }) ); // wrappedItem.tsx,在子元素中做包裹 useLayoutEffect(() => { ob.observe(myRef.current!); const ref = myRef.current!; return () => { ob.unobserve(ref); } }, [ob]);2. 实时计算滚动高度因为每个DOM元素的高度是不确定的,所以不能像固定高度那样直接数量✖️元素高度那么简单粗暴。为了计算出较为真实的滚动高度,我们需要设置一个变量itemHeights记录每个元素所渲染出来的高度,同时我们需要设置一个大概的高度ELEMENT_HEIGHT。当这个元素还没渲染的时候,默认值就是ELEMENT_HEIGHT,结合ResizeObserver,itemHeights就会越来越完善,滚动高度就会越来越真实。 useLayoutEffect(() => { let scrollH = itemHeights.reduce((sum, h) => sum += h, 0); setScrollHeight(scrollH + (list.length - itemHeights.length) * ELEMENT_HEIGHT); }, [itemHeights, list]); const sizeChange = useCallback(() => { updateScrollY(); }, [updateScrollY]); const updateScrollY = useCallback(() => { ... // 省略部分代码,下方就是循环去获取当前已渲染的DOM元素去更新。 itemHeights[anchorItem.current.index] = outerHeight(anchorDom); for (let i = domIndex + 1; i < items.length; i++) { const index = items[i].index; itemHeights[index] = outerHeight(item); } for (let i = domIndex - 1; i >= 0; i--) { const index = items[i].index; itemHeights[index] = outerHeight(item); } setItemHeights([...itemHeights]); }, [itemHeights]);3.计算可视元素的translateY值这块其实跟高度一样,都是需要依赖ResizeObserver去进行计算的,因为每个元素的translateY的值都是通过上一个元素的translateY和高度,有一股动态规划的味道了,都是依赖前一个状态的值,当然第一个元素的translateY的值是0。所以我们需要设置一个itemScrollYs去为每个元素记录translateY值,在渲染的时候直接设置style的transform。 const updateScrollY = useCallback(() => { // 更新的时候通过锚点元素开始进行记录 const items = itemRefs.current; const domIndex = Array.from(items).findIndex((item) => item.index === anchorItem.current.index); const anchorDom = items[domIndex].dom; itemHeights[anchorItem.current.index] = outerHeight(anchorDom); // 以锚点为开始设置translateY itemScrollYs[anchorItem.current.index] = containerRef.current!.scrollTop - anchorItem.current.offset; for (let i = domIndex + 1; i < items.length; i++) { // 往后计算每个元素的translateY const item = items[i].dom; if (item === null) return; const index = items[i].index; itemHeights[index] = outerHeight(item); const scrollY = itemScrollYs[index - 1] + itemHeights[index - 1]; itemScrollYs[index] = scrollY; } for (let i = domIndex - 1; i >= 0; i--) { // 往前计算每个元素的translateY const item = items[i].dom; if (item === null) return; const index = items[i].index; itemHeights[index] = outerHeight(item); const scrollY = itemScrollYs[index + 1] - itemHeights[index]; itemScrollYs[index] = scrollY; } ... // 忽略部分代码 setItemHeights([...itemHeights]); setItemScrollYs([...itemScrollYs]); }, [itemHeights, itemScrollYs]);4. 当元素高度调整完后,对滚动条的影响如何这时候估计部分人会有些许疑问? 上面的updateScrollY代码搞得花里胡哨的,根本看不懂为什么要这样做?刚开始实现的时候,我的想法非常简单。正常来说,因为每个元素translateY都是依赖上一个元素的值,因为resizeObserver可以传参告诉我,哪个元素的高度更新了。只要以这个元素开始,把它下方的元素的translateY都更新就好了,在它上面的根本就没有影响。但这种想法十分理想,但实际上在浏览器中,因为高度是渲染的时候才知道的,总体滚动高度scrollHeight是每次都会变化的。这时候如果元素渲染出来的高度是比设置的ELEMENT_HEIGHT的值相差较大时,那么浏览器就会自动帮我们调整滚动条的位置,这时候用户看到的情况是疯狂抖动!根本就毫无体验感!所以这里我们回到固定高度的实现,那时候出现了一个锚点元素的定义。对于固定高度来说是一种杀鸡焉用牛刀的感觉,而在动态高度这里,就是一个定海神针,就是为了解决抖动的问题。锚在现实中是用来帮助船舶停靠,固定船的位置,而在这里的作用,当浏览器调整滚动高度的时候,我们要通过锚点元素,即第一个可视元素的index和offset,根据容器的scrollTop来重新计算它的translateY的值,这样让用户感觉不到抖动,所以上方的updateScrollY才会如此复杂。5. scroll事件的处理scroll事件的处理因为有了锚点元素的帮助,其实跟固定高度的实现差不多,但还是有些许变化,还是要通过实时计算才能得知当前的锚点元素是否发生变化。 const updateAnchorItem = useCallback( (container) => { const delta = container.scrollTop - lastScrollTop.current; lastScrollTop.current = container.scrollTop; const isPositive = delta >= 0; anchorItem.current.offset += delta; let index = anchorItem.current.index; let offset = anchorItem.current.offset; const actualScrollHeight = itemScrollYs[lastItem - 1] + itemHeights[lastItem - 1]; ... // 省略部分代码 if (isPositive && offset > 0) { // 根据offset的值,计算下方元素的高度是否大于offset while (index < list.length && offset >= itemHeights[index]) { if (!itemHeights[index]) { itemHeights[index] = ELEMENT_HEIGHT; } offset -= itemHeights[index]; index++; } if (index >= list.length) { anchorItem.current = { index: list.length - 1, offset: 0 }; } else { anchorItem.current = { index, offset }; } } else { // 同理,offset是负数时,也是这样计算 while (offset < 0) { if (!itemHeights[index - 1]) { itemHeights[index - 1] = ELEMENT_HEIGHT; } offset += itemHeights[index - 1]; index--; } if (index < 0) { anchorItem.current = { index: 0, offset: 0 }; } else { anchorItem.current = { index, offset }; } } ... //省略部分代码 }, [itemHeights, list, updateScrollY, firstItem, itemScrollYs, scrollHeight, lastItem], )6. 收尾,磨平动态高度的最核心的算法已经完成,但还有一些收尾的工作需要完成。因为当触发ResizeObserver回调的时候,需要以锚点元素为基点,重新计算可视元素的translateY的值。这就会导致顶部的元素和尾部的元素的translateY值会有问题,第一个的元素肯呢个会出现不等于零的情况,而尾部的元素会出现大于或小于容器scrollHeight的情况。所以,剩下的工作就是要磨平这些情况,把这些收尾工作给做好。 const updateScrollY = useCallback(() => { ... // 就是锚点元素计算translateY的值 if (itemScrollYs[0] !== 0) { /** 如果滚动到上方第一个元素出现的时候, 发现第一个元素的translateY不为零的时候, 就要重新调整,因为第一个必为0 **/ const diff = itemScrollYs[0]; for (let i = 0; i < items.length; i++) { itemScrollYs[i] -= diff; } const actualScrollTop = anchorItem.current.index - 1 >= 0 ? itemScrollYs[anchorItem.current.index - 1] + anchorItem.current.offset : anchorItem.current.offset; containerRef.current!.scrollTop = actualScrollTop; lastScrollTop.current = actualScrollTop; } setItemHeights([...itemHeights]); setItemScrollYs([...itemScrollYs]); }, [itemHeights, itemScrollYs]); const updateAnchorItem = useCallback( (container) => { ... // 省略计算offset的代码 if (lastItem === list.length && actualScrollHeight < scrollHeight) { // 需要修复底部留白的问题 const diff = scrollHeight - actualScrollHeight; offset -= diff; setScrollHeight(actualScrollHeight); } ... // 省略计算锚点元素的切换 if (itemScrollYs[firstItem] < 0) { // 当firstItem的元素小于0的时候也要调整,因为容器的滚动值不为0 const actualScrollTop = itemHeights.slice(0, Math.max(0, anchorItem.current.index)).reduce((sum, h) => sum + h, 0); containerRef.current!.scrollTop = actualScrollTop; lastScrollTop.current = actualScrollTop; if (actualScrollTop === 0) { anchorItem.current = { index: 0, offset: 0 }; } updateScrollY(); } }, [itemHeights, list, updateScrollY, firstItem, itemScrollYs, scrollHeight, lastItem], )至此,动态高度的实现已经完成。点击此处跳转到demoTroubleShooting在实现动态高度的时候,有一个特别需要注意的点,请观察下方的代码: useLayoutEffect(() => { ob.observe(myRef.current!); const ref = myRef.current!; return () => { ob.unobserve(ref); } }, [ob]);这段代码其实是使用了useLayoutEffect,熟悉react的同学应该知道跟useEffect的区别,在这里一定要使用useLayoutEffect,这是为什么呢?这里仅仅是observe一个DOM元素罢了,没有任何的渲染操作。如果换成useEffect,就会发现当快速滚动的时候,会发现上方或下方会出现比较大的留白,严重时甚至会报错。首先,在动态高度ResizeObserver的处理函数updateScrollY中,需要找到锚点元素,再从锚点元素出发去更新其他可视元素的translateY,而锚点元素的更新也只依赖scroll函数。ResizeObserver是回调是一个微任务,当它第一次observe元素的时候,就会触发一次回调,后续元素的边框改变的时候,也会更新。而Scroll事件是一个宏任务,所以在执行顺序中,ResizeObserver的优先级会大于Scroll事件。回到React的原理上,React的Reconciliation分为两个阶段render和commit,而useLayoutEffect和useEffect都在commit执行,而区别是useLayoutEffect是在浏览器触发渲染之前会执行。思考一下这个场景:假设我们的容器可以容纳10个DOM元素,当用户快速滚动,这10个DOM元素都要触发更新,如果使用useEffect去observe元素的话,其实这10个元素都已经渲染完,ResizeObserver再去observe的话,浏览器的微任务队列就有10个ResizeObserver的回调已就绪,那么Scroll的事件就会被延后执行,导致锚点元素更新不及时,用户就会看到留白的情况。如果是使用useLayoutEffect去observe的时候,这时候ResizeObserver的回调则不会执行,需要等待React真正在commit阶段去append到document的时候才会执行,而这10个ResizeObserver回调不会一次性就绪,那么在它们中间就可以穿插Scroll事件,用户就看不到留白的情况,体验比较好。所以实现虚拟滚动用的原生 > Vue = React Class > React Function。参考文献 https://developers.google.com/web/updates/2016/07/infinite-scroller#the_right_thing™————————————————版权声明:本文为CSDN博主「Howard_Ching」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/cleanHtroop/article/details/123172636
-
整理 | 苏宓出品 | CSDN(ID:CSDNnews)兜兜转转,不少开发者还是发现 React Native 的真相定律。日前,国外知名聊天软件 Discord 于官方博客上发布了一则《Android 版本 Discord 激动人心的更新》公告。在公告中,Discord 指出,其将在未来几周内改进 Android 版本的应用程序,具体包括通过跨 Android、iOS 和桌面端的集中式简化应用开发流程,实现跨平台的功能一致性。iOS、桌面端、Android 步调不一,苦不堪言的跨平台开发一直以来,我们在使用软件时经常会发现不同平台上,相同的应用程序更新步伐都不太一致,往往都是 iOS 端和桌面端先行发布之后,Android 平台上的应用程序才姗姗来迟。这种现象也同样出现在了 Discord 上。如今,Discord 决定改变这种现状,其表示正在使用 React Native 来进行 Android 应用开发,这意味着在 Discord 的每个平台上都能以更快速度改进体验,同时仍然在 UI 中保留 Android 和 iOS 特定的模式。Discord Android 版使用 React Native 开发作为一款跨平台框架,React Native 是由 Facebook (现更名为 Meta)软件工程师 Jordan Walke 在 2013 年发起了的一个黑客马拉松项目,彼时他发现了一种为 iOS 应用程序生成 UI 组件的方法。因此,React 开源框架最初是为 iOS 构建的,并于 2015 年 3 月作为开源项目在 GitHub(https://github.com/facebook/react-native)上可供公众使用。随后,Facebook 也迅速为 React Native 提供了 Android 支持。截至目前,React Native 已经成为 GitHub 中最大的项目之一,拥有 104k Star 和 22.3k 分支。它也被广泛用于许多流行的移动应用程序,包括Instagram、Microsoft Outlook、Shopify、Tesla、Pinterest 等等。Discord 自 2015 年以来一直在其 iOS 应用程序中使用 React Native。如今在将 Android 应用程序开发迁移到 React Native 框架下, Discord 表示,“Android 用户还将享受更快的应用程序更新发布周期的好处。React Native 允许我们简化和整合流程,有助于工程师更高效地工作,更频繁地推送更新,特别是现在团队不会花太多时间为不同的设备维护不同的代码库。”React Native 是跨平台神器?针对 Discord 的迁移,不少网友也发起了对 React Native 的使用的讨论。@ramesh31:React Native 确实是移动开发的游戏规则改变者。去年,我用它来构建一个新的 iOS 应用程序,这是自旧的 Objective-C / UI-Kit 时代以来的第一次。实际上,我在几分钟内构建了一些东西,这在当时需要花费数小时或数天的时间编写自定义 OpenGL 和网络代码。在这一点上,任何性能权衡都是值得的。不过,由于性能、迭代速度、复杂度等维度的重重挑战,曾经也有不少公司如 Airbnb、Udacity 在使用一段时间的 React Native 之后选择了放弃,重新拥抱原生开发。对此,也有网友@Grim-444 根据自己公司的实践分享道:“我非常确信 React Native 只带来一件真正的改变,那就是它为 PM/经理提供了一些很好的宣传材料,告诉他们如何领导团队“切换到共享的跨平台代码库”。然后,他们在使用 React Native 做迁移工作时,进而需要一直处理迁移到 RN 的长期后果。以我们公司为例,近日,技术团队将 Android、iOS 应用程序从原生开发转为 React Native 开发。RN 发起人承诺在平台之间共享代码以及性能会和原生开发一样好,以及 UI 也会像原生开发一样获得各种各样的好处,然而最终这些都没有真正的实现。到了最后,RN 的代码只占了 Android 应用程序的 20%。技术团队不能实现“编写一次”,在两端运行。原来,我们有两个团队:iOS 和 Android 开发团队。现在,我们有三个团队,iOS 原生、Android 原生和 React Native 团队。进而 React Native 团队内部还被分为 iOS 和 Android 两大阵营,显得特别乱。另外使用 RN 开发还存在一些问题,如:对于在 RN 中没有办法完成的事情,我们还得需要一个配备齐全的 Android 团队的支持。在使用 React Native 开发时,与原生开发的应用程序性能差异非常明显。现在我们组织也有问题,因为应用程序涉及多个团队/语言,应用程序的不同部分由不同的团队和管理层拥有,而不是只有一个团队来开发一款好的应用程序。我们团队的不少工程师都不想与 RN 有任何关系。我们的代码库现在依赖于第三方平台和完全不同于 Google 所说的应该构建 Android 应用程序的语言。为什么我们不按照 Android 团队所说的方式构建 Android 应用程序呢?是否真的有必要长期支持 RN/JS?”为此,你怎么看?是否使用过 React Native?参考:https://news.ycombinator.com/item?id=32310392https://discord.com/blog/android-react-native-framework-update————————————————版权声明:本文为CSDN博主「CSDN资讯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/csdnnews/article/details/126132583
-
Axios是一个npm软件包,允许应用程序将HTTP请求发送到Web API,下面这篇文章主要给大家介绍了关于React项目中axios的封装与API接口的管理的相关资料,需要的朋友可以参考下目录• 前言 • 安装 • 引入 • 环境的切换 • 请求拦截 • 响应拦截 • api的统一管理 • 总结 前言在react项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。如果还对axios不了解的,可以移步axios文档 。安装1234//使用npm安装npm install axios; //使用yarn安装yarn add axios引入在项目根目录中,新建一个request文件夹,然后在里面新建一个index.js和一个api.js文件。index.js文件用来封装我们的axios,api.js用来统一管理我们的接口。123456//在index.js中引入axiosimport axios from 'axios';//引入qs模块,用来序列化post类型的数据import QS from 'qs';//antd的message提示组件,大家可根据自己的ui组件更改。import { message } from 'antd'环境的切换我们的项目环境可能有开发环境、测试环境和生产环境。我们通过node的环境变量来匹配我们的默认的接口url前缀。这里需要node的全局变量process,process.env.NODE_ENV可以区分是开发环境还是生产环境。12345//保存环境变量const isPrd = process.env.NODE_ENV == 'production'; //区分开发环境还是生产环境基础URLexport const basciUrl = isPrd ? 'https://www.production.com' : 'http://www.development.com'这里导出基础URL是为了防止有其他地方用到资源不一样,需要区分生产环境还是开发环境,导入就直接可以用了。请求拦截我们在发送请求前可以进行一个请求的拦截,为什么要拦截呢,我们拦截请求是用来做什么的呢?比如,有些请求是需要用户登录之后才能访问的,或者post请求的时候,我们需要序列化我们提交的数据。这时候,我们可以在请求被发送之前进行一个拦截,从而进行我们想要的操作。1234567891011121314151617181920212223//设置axios基础路径const service = axios.create({ baseURL: basicUrl})// 请求拦截器service.interceptors.request.use(config => { // 每次发送请求之前本地存储中是否存在token,也可以通过Redux这里只演示通过本地拿到token // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 const token = window.localStorage.getItem('userToken') || window.sessionStorage.getItem('userToken'); //在每次的请求中添加token config.data = Object.assign({}, config.data, { token: token, }) //设置请求头 config.headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' } config.data = QS.stringify(config.data) return config}, error => { return error;})这里说一下token,一般是在登录完成之后,将用户的token通过localStorage或者sessionStorage存在本地,然后用户每次在进入页面的时候(即在main.js中),会首先从本地存储中读取token,如果token存在说明用户已经登陆过,则更新Redux中的token状态。然后,在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据你携带的token来判断你的登录是否过期,如果没有携带,则说明没有登录过。响应拦截1234567891011121314151617181920212223// 响应拦截器service.interceptors.response.use(response => { //根据返回不同的状态码做不同的事情 // 这里一定要和后台开发人员协商好统一的错误状态码 if (response.code) { switch (response.code) { case 200: return response.data; case 401: //未登录处理方法 break; case 403: //token过期处理方法 break; default: message.error(response.data.msg) } } else { return response; }})//最后把封装好的axios导出export default service响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对他进行一些处理。例如上面的思想:如果后台返回的状态码是200,则正常返回数据,否则的根据错误的状态码类型进行一些我们需要的错误,具体返回的状态码需要处理那些流程还需要跟后台开发人员协商。上面的message.error()方法时我引入的antd的库提示组件,需要根据你的UI库,对应使用提示组件api的统一管理整齐的api就像电路板一样,即使再复杂也能很清晰整个线路。上面说了,我们会新建一个api.js,然后在这个文件中存放我们所有的api接口。首先我们在api.js中引入我们封装的axios12//导入我们封装好的axios import service from './index'现在,例如我们有这样一个接口,是一个post请求:1http://www.development.com/api/v1/articleEdit我们可以在api.js中这样封装:1export const apiArticleEdit = info => service.post('/api/v1/articleEdit', info);我们定义了一个apiArticleEdit方法,这个方法有一个参数info,info是我们请求接口时携带的参数对象。而后调用了我们封装的axios方法,第一个参数是我们的接口地址,第二个参数是apiArticleEdit的info参数,即请求接口时携带的参数对象。最后通过export导出apiArticleEdit。然后在我们的页面中可以这样调用我们的api接口:1234567891011121314151617181920import React, { Component } from 'react' import { apiArticleEdit } from './request/api'export default class App extends Component { componentDidMount() { // 调用api接口,并且提供了两个参数 let params = { type: 2, author: '北孤清茶' } apiArticleEdit(params).then(res => { // 获取数据成功后的其他操作 //..... console.log(res) }) } render() { return ( <div> </div> ) }}其他的api接口,就在api.js中继续往下面扩展就可以了。友情提示,为每个接口写好注释哦!!!api接口管理的一个好处就是,我们把api统一集中起来,如果后期需要修改接口,我们就直接在api.js中找到对应的修改就好了,而不用去每一个页面查找我们的接口然后再修改会很麻烦。关键是,万一修改的量比较大。还有就是如果直接在我们的业务代码修改接口,一不小心还容易动到我们的业务代码造成不必要的麻烦。好了,最后把完成的axios封装代码奉上。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162//在index.js中引入axiosimport axios from 'axios';//引入qs模块,用来序列化post类型的数据import QS from 'qs';//antd的message提示组件,大家可根据自己的ui组件更改。import { message } from 'antd' //保存环境变量const isPrd = process.env.NODE_ENV == 'production'; //区分开发环境还是生产环境基础URLexport const basciUrl = isPrd ? 'https://www.production.com' : 'http://www.development.com' //设置axios基础路径const service = axios.create({ baseURL: basicUrl}) // 请求拦截器service.interceptors.request.use(config => { // 每次发送请求之前本地存储中是否存在token,也可以通过Redux这里只演示通过本地拿到token // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 const token = window.localStorage.getItem('userToken') || window.sessionStorage.getItem('userToken'); //在每次的请求中添加token config.data = Object.assign({}, config.data, { token: token, }) //设置请求头 config.headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' } //序列化请求参数,不然post请求参数后台接收不正常 config.data = QS.stringify(config.data) return config}, error => { return error;}) // 响应拦截器service.interceptors.response.use(response => { //根据返回不同的状态码做不同的事情 // 这里一定要和后台开发人员协商好统一的错误状态码 if (response.code) { switch (response.code) { case 200: return response.data; case 401: //未登录处理方法 break; case 403: //token过期处理方法 break; default: message.error(response.data.msg) } } else { return response; }})//最后把封装好的axios导出export default service总结到此这篇关于React项目中axios的封装与API接口管理的文章就介绍到这了转载自https://www.jb51.net/article/242127.htm
-
目录• 前言 • 准备工作 • 开始封装axios • config.js • request.js • 进行请求 • 总结 前言最近写react项目使用到axios进行请求封装,在这里记录一下,希望可以帮助到初学的小伙伴准备工作进行请求需要使用axios,所以要先确定你已经安装完成了axios,查看package.json文件如果没有,请先安装axios1npm install axios开始封装axios首先在src目录下新建axios文件夹,这里我们新建两个文件,分别是config.js和request.jsconfig.js这个文件我们用来进行基础配置,区分开发环境:1.定义devBaseUrl开发环境和proBaseUrl生产环境2.使用process.env来区分开发还是生产环境,它返回的是一个包含用户的环境信息3.最后设置一下timeout1234const devBaseUrl = 'http://api.k780.com/'const proBaseUrl = 'http:xxx//xxxx.xx.com'export const BASE_URL = process.env.NODE_ENV === 'development' ? devBaseUrl : proBaseUrlexport const TIMEOUT = 5000request.js存放对应的封装方法和拦截器:1.首先导入axios和config中我们上面写的BASE_URL, TIMEOUT2.定义instance = axios.create({ }),里面存放对象3.写一个请求拦截器,在请求拦截器里面就可以做token验证之类的操作1234567891011121314import axios from 'axios'import { BASE_URL, TIMEOUT } from './config'const instance = axios.create({ baseURL: BASE_URL, timeout: TIMEOUT})// 请求拦截器instance.interceptors.request.use(config => { console.log('被拦截做一些操作') return config}, err => { return err})export default instance进行请求做完上面的封装之后,我们就可以进行请求操作了,来到要使用的页面首先进行导入刚刚写的request文件:1import request from '../../axios/request'我这里请求的是网上的一个天气接口,地址已经写在了上面config.js文件中的devBaseUrl对于api的请求,我们这里在componentDidMount()生命周期中进行异步请求:1.使用async await 进行异步操作2.使用data接收请求的结果,在request中写请求需要的params参数3.最后打印data看下结果,后面是使用setState修改值1234567891011121314async componentDidMount() { const data = await request({ // 参数 params: { app: 'weather.today', weaId: 248, appkey: 10003, sign: 'b59bc3ef6191eb9f747dd4e83c99f2a4', format: 'json' } }) console.log(data) this.setState({weather: data.data.result.weather}) }查看打印的结果:可以看到请求到的data数据,这样就说明请求成功啦总结本篇文章就到这里了,希望能够给你带来帮助转载自https://www.jb51.net/article/242127.htm
-
在React中,所谓受控组件和非受控组件,是针对表单而言的。表单受控组件(value==> state属性)表单元素依赖于状态,表单元素需要默认值实时映射到状态的时候,就是受控组件,这个和双向绑定相似.受控组件,表单元素的修改会实时映射到状态值上,此时就可以对输入的内容进行校验.受控组件只有继承React.Component才会有状态.受控组件必须要在表单上使用onChange事件来绑定对应的事件.class Control extends React.Component { // 这样的写法也是声明在实例上的对象 state = {// 给组件状态设置默认值,在实时修改时进行校验 username: "zf", pwd: "123" } // e为原生的事件绑定对象 handleChange = (e) => { // 获取原生对象上的属性 let name = e.target.name; // 根据表单元素的name名称进行匹配,比如用户名的name是username,那新输入的值将更新原来的值 this.setState({ [name]: e.target.value }) } render() { return ( <div> <p>{this.state.username}</p> 用户名:<input name="username" type="text" value={this.state.username} onChange={this.handleChange} /> <br /> <p>{this.state.pwd}</p> 密码:<input name="pwd" type="text" value={this.state.pwd} onChange={this.handleChange} /> <br /> </div> ) } } 原理:将表单 value属性绑定为 react 的状态(state的属性) value={this.state.pwd} ,使得我们可以通过表单和状态的双向绑定 非受控组件(另一种value ==> state.xxx)非受控组件即不受状态的控制,获取数据就是相当于操作DOM。非受控组件的好处是很容易和第三方组件结合。获取输入框中的值的两种方法ref 功能是一样,只是写法不一样,可以让我们操作DOM第一种方式是函数在虚拟DOM节点上使用ref,并使用函数,将函数的参数挂载到实例的属性上handleSubmit = (e) => { // 阻止原生默认事件的触发 e.preventDefault(); console.log(this.username.value); } render() { return ( <form onSubmit={this.handleSubmit}> {/* 将真实的DOM,username是输入框中输入的值赋值给组件实例上 这样,在页面表单提交的时候就可以通过this.username.value获取到输入框输入的值 */} 用户名<input name="username" type="text" ref={username=>this.username=username} /><br /> </form> ) }第二种方式:通过构造函数声明的方式react 16.3新语法实例的构造函数constructor这创建一个引用在虚拟DOM节点上声明一个ref属性,并且将创建好的引用赋值给这个ref属性react会自动将输入框中输入的值放在实例的second属性上constructor(){ super(); // 在构造函数中创建一个引用 this.second=React.createRef(); } handleSubmit = (e) => { // 阻止原生默认事件的触发 e.preventDefault(); console.log(this.second.current.value); } render() { return ( <form onSubmit={this.handleSubmit}> {/* 自动将输入框中输入的值放在实例的second属性上 */} 密码<input name="password" type="text" ref={this.second} /><br /> </form> ) }参考文章:https://juejin.cn/post/6844903629493633038
-
不久前,尤雨溪发布了 Vue 3.0 Beta 版本。发布之后我们对社区里的前端开发者做了一次调查沟通,大家普遍认为 Vue 已经具备了商业项目开发的必备条件,如语法精炼、优雅而简洁、代码的可读性高、成熟的组件模块化能够让开发者从编码中获得乐趣等等,当然,还有商业项目开发最为看重的与第三方控件的结合能力。正是这些能力,确保了“后浪” Vue 能够与 React、Angular 等老牌前端开发框架并驾齐驱,在国内开发者当中占据越来越重要的位置,逐渐有发展成为国内三大前端框架之首的趋势。不过,在读者看来,Vue 作为近几年发展最快的JS框架, 其崛起主要原因不单单是因为粉丝的过度追捧,也并不是因为某个大公司的权威推动。和 React、Angular 相比,Vue 在可读性、可维护性和趣味性之间做到了很好的平衡,结合我们之前为大家推荐过的纯前端表格控件 SpreadJS,有用户表示仅通过一周的自学就搞出来了一个可供企业内部使用的表格协同文档系统。本文我们将通过分析 Vue 的特性,谈谈为什么商业项目开发我更推荐 Vue,而不是React 和 Angular。Vue、React、Angular 优势对比这个对比表应该可以代表大部分人对于三大前端框架的理解。React 的灵活性很高,这就决定了它的上限也很高。但 React 相对于 Vue,规矩更多,为了让项目代码等规矩更有条理性,需要更多的代码来实现,假如有一天我们不在依赖一大堆npm包和ES5编译器,要做出React应用简直是难如登天。而相比 React 所强调的所谓JS纯净性和代码可读性,Angular 的确算得上一款优秀的前端框架。Angular 可以帮助我们快速进入开发,在代码的头一千行,我们会感到很有趣,但在那之后,代码将开始变得糟糕起来。大部分时间,你都会迷失在各种指令和作用域里,代码管理难度将会劝退大部分新来的开发人员。因此,Angular 的主要问题就是太难了,入门难、做项目也难,哪怕是个资深的前端工程师也会头痛,但前期的投入换来的是后期的低维护压力。对此,网上专门有人总结了一个公式: React = Think in JS, everything is JS + Data (structure) Angular = Think in OO + Patterns (lots of) + 最佳实践Vue 很好的借鉴了二者的设计理念,并融会贯通。对于大部分开发者来说,它优雅而简洁,可以让我们把注意力集中在解决问题,而非代码逻辑上。Vue 的独特优势Vue 和其他前端框架相比,在结构、样式、业务分离等方面更清晰彻底,更符合前端多年来的编码习惯,更符合直觉、更容易学习和维护。入门非常容易,资料丰富,框架功能完善,加入非常多的特性,例如,if, for, async,为开发者节省很多垃圾代码。模板支持 html 和 jsx,支持自定义指令,方便操作 dom 的一致行为。一、门槛低、上手快Vue 上手简单的原因是无需复杂配置,只需要一个 HTML 与相关文件就能跑起来。从设计的角度上来看,Vue 考虑的也是如何降低门槛,让只掌握了 Web 基础知识 (HTML, CSS, JS) 的情况下,能够最快理解和上手,从而实现和完成一个应用。和 React、Angular 相比,Vue 的中文文档是写的最好的,再加上国内有非常丰富的视频、图文教程、各种开源的插件,哪怕是一个新手前端开发,学习一周左右就可以搞一个项目出来。就如我们开头所说,配合第三方前端表格控件 SpreadJS,自学一周就能做出一个企业级的表格协同文档。所以,它对于非专业前端,或者前端入门人士来说是非常适合的。其次,Vue 设定多,所以需要思考的就少。属性指令定义了一大堆,API 文档整理好的就在那里,需要什么一查,最佳实践的 demo 写好了放在那边,照着写就 OK 了。二、人性化,符合用户习惯React 的设计理念是提供强大而复杂的机制,让开发者来适应我;而 Vue 则是为了更适应开发者的使用习惯,在很多设定上都是让开发者怎么爽怎么来。比如 Vue 的 API 跟传统 Web 开发者熟悉的模板契合度非常高。Vue 的单文件组件是以模板+JavaScript+CSS 的组合模式呈现,它跟 Web 现有的 HTML、JavaScript、CSS 能够更好地配合;Vue 提供反应式的数据,当数据改动时,界面就会自动更新,而 React 里面则还需要调用方法 SetState。三、Vue + 第三方控件 = 效率高 & 使用便利 & 组件化架构前面我们提到了 Vue 的两个基础特性,但能成为时代的发展趋势, 说明 Vue 的能力远不止于此。从我们团队使用 Vue 的情况来看,Vue 使用起来异常简单,它从 React 那里借鉴了组件化、prop、单向数据流、性能、虚拟渲染,并意识到状态管理的重要性,并从 Angular 那里借鉴了模板,并赋予了更好的语法,以及双向数据绑定(在单个组件里),它不强制使用某种编译器,所以你完全可以在遗留代码里使用Vue,并对之前乱糟糟的jQuery代码进行改造。即便,仍然有很多人认为 Vue 只适合开发简单的网站或者单页面应用,但其实 Vue 有着比 React 和 Angular 更为丰富多元的第三方控件资源。配合这些资源使用,Vue 做企业级项目甚至比其他框架来的更加便利高效。这里我们还是用第三方控件 SpreadJS 举例,看看苏宁易购是如何在短短一周内,搭建并上线一个企业级的表格协同文档系统的。通过嵌入 SpreadJS 在线编辑器,开发的系统界面苏宁易购作为我国领先的 O2O 智慧零售商,在实现协同办公之前处理内部信息的办法是:在 Excel 上安装插件,通过插件与数据库通信,实现数据权限管控,这样做非常的低效且混乱。为了提升效率实现真正的协同办公,它们采购了 SpreadJS 纯前端表格控件,为其构建基于 Web 端 + Vue 集成的 Excel 数据管理系统 —— 「极客办公平台」。极客办公平台界面截图之所以选用 Vue 作为前端框架,是因为 Vue 本身通过提供现成的范式让整个项目的搭建过程更加快捷。题外话:其实在选择 Vue 之前,项目组已经尝试过 React 框架,但是一次又一次的整理prop和重构微组件的过程让其痛不欲生。而之所以选用 SpreadJS ,是因为它已经实现了微软 Office Excel 90% 以上的内置功能,开发人员无需安装任何软件,只需增加一些 UI 样式和下拉框,就可以迅速交付一套完整的基于 Web 的 Excel 功能模块。据苏宁易购系统架构师候健的分享所述,为保证新老系统顺利过渡,需要投入人力,完成大量的 Excel 数据迁移工作,因此,新老系统对 Excel 文件的兼容性至关重要。而借助 SpreadJS 纯前端无损导入导出 Excel 这一产品特性,极客办公平台才得以顺利完成交付并迅速投入使用。点击此处,了解 SpreadJS与Vue集成,苏宁集团“极客办公”系统的开发案例。为什么选择 Vue + SpreadJS?使用 Vue + SpreadJS 可以快速搭建一套在线协同表格文档,无论是对于文档的性能、项目研发效率,以及后期维护成本和新老系统数据迁移成本来说,它们都是一对“最佳拍档”。SpreadJS 提供了类 Excel 的操作界面和开放的 API,将其嵌入系统,可快速实现 Excel 导入导出、公式计算、在线填报数据、打印报送、实时预览、数据校验、服务端数据交互等功能,通过对其二次扩展,可以将 SpreadJS 作为在线文档协同编辑系统的核心模块,满足多人协作、实时编辑、数据同步、多级上报、历史查询等业务需求。经过实测,借助 Vue + SpreadJS,可以实现用不到 100 行代码,将 Excel 的功能和使用体验完美嵌入到在线文档系统中。如上面苏宁易购的例子,在 Vue 对应的页面组件 mount 中重新调用初始化方法,实现高度类似 Excel 的表单布局,仅需如下代码:Vue —— 契合当下的大势所趋我们已经在过去无数次尝试使用 Vue 为不同的项目开发了很多代码,结果也很令人满意(每个项目周期不超过 3 个月)。也许,3 个月对于后端开发来说算不上什么,但在JS世界里,它举足轻重。文无第一武无第二,无谓的争论谁比谁好并没有什么意义,本文也并不是希望将这三个框架分个高下,而是希望可以给大家提供更多参考信息,根据自身项目的实际需求来选择更适合的框架。从客观事实来说,最具创新力的是 React,而最具企业级能力的是 Angular,能够取长补短,各项数据介于两者之间的是 Vue。正是因为 Vue 具备门槛低、易上手、人性化、效率高等特点,外加有着最为丰富的中文资源和诸如 SpreadJS 等优秀的第三方控件加持,开发者们得以实现项目的“短平快”开发。在这个追求「快速」和「变化」的时代,这款前端框架的定位明显更契合时代的主流需求。即便 Vue 的作者尤雨溪曾多次表示,如果多年以后要论历史地位,React 肯定是高于 Vue 的。不过历史地位并不是开发者需要考虑的问题,我们可以为某一个编程语言、某一种框架的社区发展贡献力量,但在实际的项目中,不能盲目的被束缚住。文中资源扩展阅读:Vue 官方文档:https://cn.vuejs.org/v2/guide/SpreadJS 官网:https://www.grapecity.com.cn/developer/spreadjs苏宁集团开发案例:https://www.grapecity.com.cn/blogs/spreadjs-system-development-case-of-suning
-
【引言】前段时间写过一篇关于前端技术的概括性文章《前端技术的选择]》(http://3ms.huawei.com/km/blogs/details/7971337),本文就对于当下顶级的前端开发技术做个相对详尽的探究,目的是为了解开这些前端技术的面纱,看看各自的庐山真面目。 【React】React(也被称为React.js或ReactJS)是一个用于构建用户界面的JavaScript库。它由Facebook和一个由个人开发者和公司组成的社区来维护。 React可以作为开发单页或移动应用的基础。然而,React只关注向DOM渲染数据,因此创建React应用通常需要使用额外的库来进行状态管理和路由,Redux和React Router分别是这类库的例子。基本用法下面是一个简单的React在HTML中使用JSX和JavaScript的例子。 <div id="myReactApp"></div> <script type="text/babel"> function Greeter(props) { return <h1>{props.greeting}</h1>; } var App = <Greeter greeting="Hello World!" />; ReactDOM.render(App, document.getElementById("myReactApp"));</script> Greeter函数是一个React组件,它接受一个属性问候语。变量App是Greeter组件的一个实例,其中问候语属性被设置为 "Hello World!"。然后,ReactDOM.render方法将我们的Greeter组件渲染在DOM元素(id为 myReactApp)中。 在web浏览器中显示时,结果将是: <div id="myReactApp"> <h1>Hello World!</h1></div> 显著特点 组件化React代码由称为组件的实体组成。组件可以使用React DOM库渲染到DOM中的一个特定元素。当渲染一个组件时,可以传入被称为 "props "的值。 ReactDOM.render(<Greeter greeting="Hello World!" />, document.getElementById('myReactApp')); React中声明组件的两种主要方式是通过功能函数组件和基于类的组件。 功能函数组件 功能组件是用一个函数声明,用来返回一些JSX。 const Greeting = (props) => <div>Hello, {props.name}!</div>; 类组件基于类的组件是使用ES6类来声明的。它们也被称为 "有状态 "组件,因为它们的状态可以在整个组件中保持,并且可以通过props传递给子组件。class ParentComponent extends React.Component { state = { color: 'green' }; render() { return ( <ChildComponent color={this.state.color} /> ); }} 虚拟 DOM另一个值得注意的特点是React使用了虚拟文档对象模型,也就是虚拟DOM。React创建了一个内存中的数据结构缓存,计算得出变化差异,只渲染实际变化的子组件, 从而高效地更新浏览器显示的DOM。 生命周期方法生命周期方法是指在组件的生命周期内,允许在设定的点执行代码的hooks处理函数。 l shouldComponentUpdate允许开发者在不需要渲染的情况下,通过返回false来防止不必要的重新渲染组件。l componentDidMount是在组件 "挂载 "后调用的(组件已经在用户界面中创建了,通常是通过将其与DOM节点关联起来)。这通常用于通过API从远程数据源触发数据加载。l componentWillUnmount是在组件被拆解或 "解挂 "之前立即调用的。这通常用于清除组件的资源依赖关系,这些依赖关系不会随着组件的卸载而简单地被移除(例如,移除任何与组件相关的setInterval()实例,或者因为组件的存在而在 "文档 "上设置的 "eventListener")。l render是最重要的生命周期方法,也是任何组件中唯一必须存在的方法。它通常在每次更新组件的状态时都会被调用。 JSXJSX,即JavaScript XML,是对JavaScript语言语法的扩展。JSX在外观上类似于HTML,它提供了一种开发者熟悉的语法结构化组件渲染的方法。React组件通常是使用JSX编写的,尽管不一定非要使用JSX(组件也可以用纯JavaScript编写)。JSX类似于Facebook为PHP创建的另一种名为XHP的扩展语法。 JSX代码的一个例子:class App extends React.Component { render() { return ( <div> <p>Header</p> <p>Content</p> <p>Footer</p> </div> ); }} 嵌套元素同一层次上的多个元素需要被包裹在一个容器元素中,如上图中的<div>元素。 属性JSX提供了一系列的元素属性,旨在对应HTML提供的属性。这些自定义的属性也可以传递给组件,所有的属性都会被组件作为props接收。 JavaScript表达式JavaScript表达式(但不是语句)可以在JSX内部通过大括号{}使用。<h1>{10+1}</h1> 上面代码的显示结果是:<h1>11</h1> 条件语句If-else语句不能在JSX中使用,但可以使用条件表达式来代替。下面的例子当i为1时将 { i === 1 ? 'true' : 'false' } 呈现为字符串 'true'。 class App extends React.Component { render() { const i = 1; return ( <div> <h1>{ i === 1 ? 'true' : 'false' }</h1> </div> ); }} 结果会是:<div> <h1>true</h1></div> 函数和JSX可以用于条件表达式中:class App extends React.Component { render() { const sections = [1, 2, 3]; return ( <div> {sections.length > 0 && sections.map(n => ( /* 'key'必须唯一 */ <div key={"section-" + n}>Section {n}</div> ))} </div> ); }} 结果会是: <div> <div>Section 1</div> <div>Section 2</div> <div>Section 3</div></div> 用JSX编写的代码需要被Babel等工具进行转换以后才能被Web浏览器所理解,这种处理一般是在软件构建过程中进行的,然后再部署构建后的应用程序。 超越HTML的架构React的基本架构不仅仅适用于在浏览器中渲染HTML。例如,Facebook有动态图表,可以渲染到<canvas>标签,而Netflix和PayPal使用通用加载,在服务器和客户端上渲染相同的HTML。 React HooksHooks是让开发者从函数组件中 "钩入"React状态和生命周期特性的函数。它们使代码具有更强的可读性且更易理解。Hooks并不在类组件内工作,它的终极目标是在React中消除类组件的存在。 React提供了一些内置的Hooks,如useState、useContext、useReducer和useEffect等。它们都在Hooks API参考书中做了说明。使用最多的是useState和useEffect,分别在React组件中控制状态和检测状态变化。 Hooks规则 Hooks也有一些规则,在使用Hooks之前必须遵循这些规则: l 钩子只能在顶层调用(不能在循环或if语句中调用)。l 钩子只能在React函数组件中调用,不能在普通函数或类组件中调用。 定制Hooks构建自己的Hooks,也就是所谓的自定义Hooks,可以让你把组件逻辑提取到可重用的函数中。自定义钩子是一个名称以 "use "开头的JavaScript函数,它可以调用其他的钩子。钩子的规则也适用于它们。 常用术语 React并没有试图提供一个完整的 "应用程序库"。它是专门为构建用户界面而设计的,因此并不包括许多一些开发者认为构建应用程序所需的工具。这使得开发者可以选择任何一个库来完成诸如执行网络访问或本地数据存储等任务。这种情况也就决定了React技术在创建网页应用时标准无法统一。 Flux架构的使用为了支持React的单向数据流的概念(与AngularJS/Angular的双向数据流形成对比),Flux架构是流行的模型-视图-控制器(MVC)架构的具有代表性的替代方案。Flux的特点是,数据动作通过中央调度器发送到一个存储仓库,而对存储仓库数据的变化会被传送回视图。当与React一起使用时,这种传送是通过组件属性完成的。 Flux可以被认为是观察者模式的一个变种。 Flux架构下的React组件不应该直接修改传递给它的任何props,而是应该传递回调函数,这些回调函数可以创建由调度器发送的数据动作来修改存储仓库。数据动作是一个对象,其职责是描述已经发生的事情:例如,一个数据动作描述的是一个用户 "follow"另一个用户。它可能包含如下数据:用户ID,目标用户ID,以及USER_FOLLOWED_ANOTHER_USER枚举类型。存储仓库,是一个数据模型,可以根据从调度器接收到的数据动作来改变自己。这种模式有时被表述为 "属性向下流动,数据动作向上流动"。自Flux诞生以来,Flux的许多实现被创造出来,其中最著名的是Redux,它的特点是单一的存储仓库,通常被称为单一的数据真相源。 历史React是由Facebook的软件工程师Jordan Walke创建的,受PHP的HTML组件库XHP的启发,发布了React的早期原型,名为 "FaxJS",。它于2011年首次部署在Facebook的News Feed上,后来于2012年部署在Instagram上。2013年5月在美国JSConf大会上开源。 React Native是2015年2月在Facebook的React Conf上宣布的,2015年3月开源的React Native,实现了原生的Android、iOS和UWP开发。 2017年4月18日,Facebook宣布了React Fiber,这是React库的一个新的核心算法,用于构建用户界面,React Fiber将成为React库未来任何改进和功能开发的基础。 2017年9月26日,React 16.0正式对外发布。 2019年2月16日,React 16.8正式对外发布,该版本引入了React Hooks。 常用命令 创建工程:npx create-react-app my-app开发环境运行:npm start 生产环境打包:npm run build 【官方网站】http://reactjs.org/ 【最新版本】16.13.1于2020年3月19日【授权】MIT License 【Angular】Angular(通常被称为 "Angular 2+"或 "Angular v2及以上版本")是一个基于TypeScript的开源Web应用框架,由Google的Angular团队和由个人以及企业组成的社区领导。 Angular是由构建AngularJS的同一个团队从零开始重写的。 Angular和AngularJS的区别 l Angular没有 "Scope"或控制器的概念,相反,它使用组件的层次结构作为其主要的架构特征。l Angular有不同的表达式语法,重点是"[]"用于属性绑定,"() "用于事件绑定l 模块化 - 许多核心功能已转移到模块上l Angular推荐使用微软的TypeScript语言,它引入了以下特性。n 静态键入,包括Genericsn 注解l TypeScript是ECMAScript 6 (ES6)的超集,向后兼容ECMAScript 5(即:JavaScript)。l 动态加载l 异步模板编译l 由RxJS提供的迭代回调。RxJS限制了状态的可见性和调试,但这些问题可以通过像ngReact或ngrx这样的反应式附加组件来解决。l 支持Angular Universal,可以在服务器上运行Angular应用程序。 历史命名最初,AngularJS的重写被称为 "Angular 2",但这导致了开发人员的迷糊。为了澄清,团队宣布,每个框架使用不同的术语,其中 "AngularJS "指的是1.X版本, "Angular " 指的是2及以上版本。 版本2Angular 2.0在2014年10月22-23日的ng-Europe大会上宣布。2.0版本的剧烈变化在开发者中引起了相当大的争议。 2015年4月30日,Angular开发者宣布Angular 2从Alpha转为开发者预览版,2015年12月Angular 2转为Beta版,2016年5月发布了第一个发布候选版本,2016年9月14日发布了最终版本。 版本42016年12月13日Angular 4发布,跳过了3,避免了因路由器包的版本错位导致的混乱,当时已经发布的版本为v3.3.0。最终版本于2017年3月23日发布,Angular 4向后兼容Angular 2。 Angular 4.3版本是一个小版本,它是4.x.x版本的替换版本。 4.3版本的功能 l 介绍了HttpClient,一个更小、更容易使用、更强大的HTTP请求库。l 为守护者和解析器提供了新的路由器生命周期事件。四个新事件。GuardsCheckStart、GuardsCheckEnd、ResolveStart、ResolveEnd加入了现有的NavigationStart等生命周期事件集。l 有条件地禁用动画。 版本5Angular 5于2017年11月1日发布,Angular 5的主要改进包括支持渐进式Web应用、构建优化器以及与Material Design相关的改进。版本6Angular 6于2018年5月4日发布。这个版本,关注的重点不在于底层框架,更多的是工具链,以及让Angular在未来的更新和升级更加容易,比如:ngupdate、ng add、Angular元素、Angular Material+CDK组件、Angular Material入门组件、CLI工作区、库支持、树形摇动提供者、动画性能提升、RxJS v6。版本7Angular 7已于2018年10月18日发布。更新内容涉及到应用性能、Angular Material & CDK、虚拟滚动、Selects的可访问性改进、现在支持自定义元素使用Web标准的内容投影,以及关于Typescript 3.1、RxJS 6.3、Node 10(仍支持Node 8)的依赖性更新。版本8Angular 8已于2019年5月28日发布。具有所有应用代码的差异化加载、惰性路由的动态导入、Web工作者、TypeScript 3.4支持、以及Angular Ivy作为预览版可配置使用。Angular Ivy预览包括: l 生成的代码,在运行时更容易阅读和调试。l 更快的重建时间l 减少有效载荷l 改进了模板类型检查l 向后兼容 版本9Angular 9已于2020年2月6日发布。第9版在默认情况下使用Ivy编译器。Angular可以与TypeScript 3.6和3.7兼容。除了数百个bug修复之外,Ivy编译器和运行时还提供了许多优势: l 更小的软件包l 更快的测试l 更好的调试l 改进的CSS类和样式绑定l 改进的类型检查l 改善了构建错误l 改善了构建时间,默认开启AOT功能l 提高国际化功能 特点组件化一个组件例子Html部分 <h2>Products</h2> <div *ngFor="let product of products"> <h3> {{ product.name }} </h3> </div> Typescript部分export class ProductListComponent { products = products;} 路由 @NgModule({ imports: [ BrowserModule, ReactiveFormsModule, RouterModule.forRoot([ { path: '', component: ProductListComponent }, { path: 'products/:productId', component: ProductDetailsComponent }, ]) ], 数据管理定义服务类export class CartService { items = []; constructor( private http: HttpClient ) {} addToCart(product) { this.items.push(product); } getItems() { return this.items; } clearCart() { this.items = []; return this.items; } getShippingPrices() { return this.http.get('/assets/shipping.json'); }}调用服务类export class ShippingComponent implements OnInit { shippingCosts; constructor( private cartService: CartService ) { } ngOnInit() { this.shippingCosts = this.cartService.getShippingPrices(); } }常用命令 从终端上,全局安装Angular CLI:npm install -g @angular/cli 使用 ng new 命令创建一个新的 Angular CLI 工作区:ng new my-project-name 开发环境运行:ng serve 生产环境打包:ng build --prod 【官方网站】https://angular.io/ 【最新版本】9.1.2于2020年4月15日【授权】MIT License 【Vue】 Vue.js(通常被称为Vue;发音为/vjuː/,类似于 "view")是一个开源的Model-view-viewmodel JavaScript框架,用于构建用户界面和单页面应用程序。它由Evan You创建,由他和来自Netlify和Netguru等多家公司的核心成员维护。 概述 Vue.js的特点是,它采用了一个渐进式的架构,专注于声明式渲染和组件合成。复杂应用所需的高级功能,如路由、状态管理和构建工具等,都是通过官方维护的支持库和包提供的,其中Nuxt.js是最受欢迎的解决方案之一。 Vue.js可以让你用称为指令(directives)的HTML属性来扩展HTML。 历史Vue是由Evan You创建的。在Google工作期间,他使用AngularJS技术参与了多个项目的开发的,之后创建了Vue。他后来总结了自己的思考过程。"我想,如果我可以把AngularJS真正优秀的部分提取出来,然后构建一些轻量级的东西,会怎么样呢?" 项目的第一个版本源码提交日期是2013年7月,Vue在2014年2月首次发布。 特点组件化Vue 组件扩展了基本的 HTML 元素来封装可重用的代码。从高层次的角度看,组件是Vue编译器附加行为的自定义元素。在Vue中,组件本质上就是一个带有预设选项的Vue实例。下面的代码片段包含了一个Vue组件的例子。该组件显示了一个按钮,并打印出按钮被点击的次数。 <div id="tuto"> <button-clicked v-bind:initial-count="0"></button-clicked></div> <script>Vue.component('button-clicked', { props: [ "initialCount" ], data: () => ({ count: 0, }), template: `<button v-on:click="onClick">Clicked {{ count }} times</button>`, computed: { countTimesTwo() { return this.count * 2; } }, watch: { count(newValue, oldValue) { console.log(`The value of count is changed from ${oldValue} to ${newValue}.`); } }, methods: { onClick() { this.count += 1; } }, mounted() { this.count = this.initialCount; }}); new Vue({ el: '#tuto',});</script> 模板Vue使用基于HTML的模板语法,允许将渲染的DOM绑定到Vue实例的底层数据。所有 Vue 模板都是有效的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。Vue 将模板编译成虚拟 DOM 渲染函数。虚拟文档对象模型(或 "DOM")允许Vue在更新浏览器之前在其内存中渲染组件。结合反应式系统,Vue能够计算出需要重新渲染的组件的最小数量,并在App状态发生变化时,启动最小量的DOM操作。 Vue用户可以使用模板语法,也可以选择使用JSX直接编写渲染函数,渲染函数允许从软件组件中构建应用程序。 反应式系统Vue的特点是采用了反应式系统,它使用纯JavaScript对象和优化的重渲染。每个组件在渲染过程中都会跟踪其反应式的依赖关系,因此系统可以精确地知道什么时候重新渲染,以及哪些组件需要重新渲染。 变换效果当从DOM中插入、更新或删除项目时,Vue提供了多种方法来部署变换效果。这包括了以下工具: l 自动应用CSS变换和动画的类l 集成第三方CSS动画库,如Animate.css等。l 在变换hooks期间,使用JavaScript直接操作DOM。l 集成第三方JavaScript动画库,如Velocity.js等。 当在变换组件中的元素**入或移除时,会出现这样的情况:l Vue会自动检测到目标元素是否应用了CSS变换或动画。如果有,CSS变换类将在适当的时间添加/删除。l 如果变换组件提供了JavaScript hooks,这些hooks将在适当的时间被调用。l 如果没有检测到CSS变换/动画,并且没有提供JavaScript hooks,那么插入和/或移除的DOM操作将在下一帧中立即执行。 路由单页面应用程序(SPA)的一个传统缺点是无法分享到特定网页中的确切 "子 "页面的链接。由于SPA只向用户提供一个基于URL的服务器响应(它通常服务于index.html或index.vue),因此通常情况下,将某些屏幕作为书签或分享到特定部分的链接是很困难的,甚至是不可能的。为了解决这个问题,许多客户端路由器用 "hashbang"(#!)来划分动态URL,例如page.com/#!/。然而,在HTML5中,大多数现代浏览器都支持不使用hashbang的路由。 Vue提供了一个界面,可以根据当前的URL路径来改变页面上显示的内容 – 可以有多种方式(无论是通过电子邮件链接、刷新还是页面内链接)。此外,当某些浏览器事件(如点击)发生在按钮或链接上时,使用前端路由器可以有意识地转换浏览器路径。Vue本身并没有自带前端路由。但开源的 "vue-router "包提供了一个API来更新应用程序的URL,支持返回按钮(导航历史记录),并支持电子邮件密码重置或电子邮件验证链接的认证URL参数。它支持将嵌套路由映射到嵌套组件,并提供精细化的过渡控制。添加了vue-router后,组件只需映射到它们所属的路由,父/根路由必须指明子路由的渲染位置。 <div id="app"> <router-view></router-view></div>... <script>...const User = { template: '<div>User {{ $route.params.id }}</div>'} const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ]})...</script> 上面的代码: l 在websitename.com/user/<id>中设置一个前端路径。l 这将在(const User...)中定义的User组件中呈现。l 允许用户组件使用$route对象的params键输入用户的特定ID:$route.params.id。l 这个模板(根据传递到路由器中的参数变化)将被渲染到DOM的div#app里面的<router-view></router-view>。l 最后生成的HTML将是:websitename.com/user/1: <div id="app"> <div> <div>User 1</div> </div></div> 生态系统核心库自带的工具和库都是由核心团队和贡献者开发的。 官方工具l Devtools - 用于调试Vue.js应用程序的浏览器devtools扩展。l Vue CLI - 用于快速开发Vue.js的标准工具书l Vue Loader - 一个webpack加载器,允许以单文件组件(SFCs)的格式编写Vue组件。 官方程序库 l Vue Router - Vue.js的官方路由器l Vuex – 基于 Flux模式的 Vue.js 的集中式状态管理。l Vue Server Renderer - 用于 Vue.js 的服务器端渲染。 常用命令安装工具npm install -g @vue/cli 创建工程:vue create my-project 开发环境运行:npm run serve 生产环境打包:npm run build 【官方网站】https://vuejs.org/ 【最新版本】2.6.1于2019年12月13日【授权】MIT License 【小结】本文对于当前顶级的前端技术做了较为详尽的探索,前端技术一个大的方向是单页应用 (SPA,Single Page Application),我们在选取针对本业务的前端技术时需要结合如下几个方面来考虑: 1. 成员当前技能,这是一个很现实的问题,大多数程序员会选择自己比较熟悉的技术。这里要思考一下,目前自己熟悉的技术是不是最优选项?2. 可利用的学习时间,如果发现要使用的技术需要一些时间学习,这个时间的开销到底会不会与开发进度有冲突?3. 能否保证项目的复杂度最低,这个是比较关键的因素。先进技术之所以先进就是因为可以让开发者把时间和精力放在真正的业务开发上面来,如果要使用的技术需要进行很多与业务不相关的配置,就需要问一个问题,有没有更好的办法? 最后,希望本文对现有或者以后的业务开发有指导或者借鉴作用。
-
一、前言需要在阿里云服务器部署Django-restframework框架,一开始不清楚情况,网上找了很多的文章和办法,东拼西凑也没有能够完全实现nginx和uwsgi的互通。参考过的文章有-视频:Nginx + uWsgi 部署 Django + Mezzanine 生产服务器-文章:uWSGI+django+nginx的工作原理流程与部署历程-文章:uwsgi官方文档-文章:Django Nginx+uwsgi 安装配置-文章:centos7 下通过nginx+uwsgi部署django应用二、网上文章的遗漏因为是东拼西凑,所以无论是网上的文章还是自己拼凑的配置,都是没有办法打通的。后来红包求助,才了解到有这几个地方:1、nginx执行权限2、uwsgi配置3、uwsgi设置虚拟环境4、uwsgi安装问题及插件安装问题5、django静态文件收集处理6、三、部署安装记录1、创建非管理员账户由于安全需求,还是配置一个非管理员(自己操作,增加sudo授权)账户操作,通过命令创建用户名密码adduser quinns # 新增用户 passwd quinns # 为quinns设置密码 复制代码设置好之后,还需要开启sudo权限,通过命令:vi /etc/sudoers 复制代码然后找到有 root ALL=(ALL)那一行,然后在下面增加一行:quinns ALL=(ALL) ALL 复制代码保存即可。下面的操作,用新用户quinns登录来操作。2、安装依赖uwsgi和nginx以及anaconda的安装会存在一些报错问题,这里为了避免出现这情况,所以先安装好依赖。sudo yum install gcc-c++ sudo yum install wget openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel libxml* pcre-devel python-devel bzip2 复制代码3、安装软件通过quinns账户登录,然后到quinns用户目录(/home/quinns)下新建一个utils目录,把一些软件下载在utils目录下。3.1安装anaconda通过wget方式下载anaconda(官网)wget https://repo.continuum.io/archive/Anaconda3-5.0.1-Linux-x86_64.sh 复制代码如果想更快,就安装国内源(清华镜像):wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-5.1.0-Linux-x86_64.sh 复制代码下载好之后sh安装sh Anaconda3-5.0.1-Linux-x86_64.sh 复制代码一路默认,到之后面安装完的时候会提示是否添加环境变量,输入yes即可。如果想要后面使用更快,可以更改仓库镜像(我没试过):conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --set show_channel_urls yes 复制代码这样的话安装软件都是国内源,速度比较快(听说是)3.2验证是anaconda否成功安装通过命令:conda list 复制代码来验证是否成功安装并加入环境变量,如果出现list列表则代表成功,如果出现报错提示信息则需要用命令:source ~/.bashrc 复制代码来添加,然后重复conda list命令。如果还是不行,则编辑/etc/profile文件,在底部添加环境变量及指向:export PATH=/home/quinns/anaconda3/bin:$PATH 复制代码通过文件添加的环境变量需重启服务器才能生效 sudo reboot3.3安装uwsgi在确认安装好anaconda之后,先不着急新建虚拟环境,直接在linux下输入python,检查默认python是否已自动替换为python3.6。接着通过pip安装uwsgi:pip install uwsgi 复制代码如果不成功则尝试使用aliyun的源 阿里云的源我复制下来了,是:http://mirrors.aliyun.com/pypi/packages/a2/c9/a2d5737f63cd9df4317a4acc15d1ddf4952e28398601d8d7d706c16381e0/uwsgi-2.0.17.1.tar.gz 复制代码待有安装成功的提示出来,再通过命令:uwsgi --version 复制代码来确认是否成功安装。3.4创建虚拟环境通过anaconda来创建python虚拟环境:conda create --name envname python=3.6.3 (亲身经历 3.6.5无法启动uwsgi,最好还是3.6.3) 复制代码观察过程,无报错即完成安装。3.5安装nginx直接通过yum来安装nginx即可,如果想安装新版,可以在网上寻找新方法。sudo yum install nginx 复制代码如果是centos7 是默认没有Nginx源的,需要给它添加源,才能使用Yum install 安装sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 复制代码安装完成后应该是自动启动服务,在浏览器输入ip即可访问nginx的欢迎页面。如果没有,通过命令:sudo service nginx start/restart 复制代码来启动或者重启nginx服务。3.6安装mysql先下载mysql的repo源wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm 复制代码接着安装mysql-community-release-el7-5.noarch.rpm包sudo rpm -ivh mysql-community-release-el7-5.noarch.rpm 复制代码安装这个包后,会获得两个mysql的yum repo源:/etc/yum.repos.d/mysql-community.repo,/etc/yum.repos.d/mysql-community-source.repo。最后执行安装sudo yum install mysql-server 复制代码3.7重置mysql密码先授权sudo chown -R root:root /var/lib/mysql service mysqld restart # 然后重启 复制代码接着重置密码mysql -u root //直接回车进入mysql控制台 mysql > use mysql; mysql > update user set password=password('quinns') where user='root'; mysql > exit; service mysqld restart # 然后再重启一次服务 复制代码3.8重点说明重点:在实际的应用当中,我们一般不推荐使用root账户,而是新增用户并对其进行授权。所以,这里我要加上删除线mysql默认是不开启远程访问的,想要在本地连接服务器的mysql,必须开启:mysql -u root -pmysql> use mysql;mysql> update user set host = '%' where user = 'root';service mysqld restart # 这里也要重启一次服务(过30秒或者1分钟再测试远程连接)如果不行的话,接着重启服务一次。3.9 新增用户并授权a.创建用户CREATE USER 'username'@'host' IDENTIFIED BY 'password'; 复制代码命令含义说明:username:你将创建的用户名 host:指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost,如果想让该用户可以从任意远程主机登陆,可以使用通配符% password:该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器 复制代码比如这里我可以把命令改成:CREATE USER 'quinns'@'%' IDENTIFIED BY '123456'; 复制代码意味着我新建了一个名为quinns且密码为123456的用户,并给它开启了所有ip地址远程连接(当然也可以指定某个ip)b.用户授权GRANT privileges ON databasename.tablename TO 'username'@'host' 复制代码命令含义说明:privileges:用户的操作权限,如SELECT,INSERT,UPDATE等,如果要授予所有则使用ALL databasename:数据库名 tablename:表名,如果要授予该用户对所有数据库和表的相应操作权限则可用*表示,如*.* 复制代码授权命令示例:GRANT SELECT, INSERT ON test.user TO 'pig'@'%'; GRANT ALL ON *.* TO 'pig'@'%'; GRANT ALL ON maindataplus.* TO 'pig'@'%'; 复制代码c.置与更改用户密码命令:SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword'); 复制代码如果是当前登陆用户用:SET PASSWORD = PASSWORD("newpassword"); 复制代码例子:SET PASSWORD FOR 'pig'@'%' = PASSWORD("123456"); 复制代码d撤销用户权限命令:REVOKE privilege ON databasename.tablename FROM 'username'@'host'; 复制代码说明:privilege, databasename, tablename:同授权部分 复制代码例子:REVOKE SELECT ON *.* FROM 'pig'@'%'; 复制代码注意:假如你在给用户'pig'@'%'授权的时候是这样的(或类似的):GRANT SELECT ON test.user TO 'pig'@'%',则在使用REVOKE SELECT ON *.* FROM 'pig'@'%';命令并不能撤销该用户对test数据库中user表的SELECT 操作。相反,如果授权使用的是GRANT SELECT ON *.* TO 'pig'@'%';则REVOKE SELECT ON test.user FROM 'pig'@'%';命令也不能撤销该用户对test数据库中user表的Select权限。 复制代码具体信息可以用命令SHOW GRANTS FOR 'pig'@'%'; 复制代码查看。e删除用户命令:DROP USER 'username'@'host'; 复制代码参考来源:传送门四、uwsgi服务测试安装好这些软件后,需要确保独立服务都是正常运行的。在/home/quinns目录下新建wwwroot目录,然后在里面新建一个测试文件uwsgitest.pydef application(env, start_response): start_response('200 OK', [('Content-Type','text/html')]) return [b"Hello World, This uwsgi server is running"] 复制代码保存后通过命令来启动uwsgi --http :8000 --wsgi-file uwsgitest.py 复制代码看到服务启动后,就可以在浏览器访问8080端口,如果能够正常显示文字内容,则代表uwsgi单独服务是可以正常运行的。如果没有,根据报错找原因。五、上传django-rest项目可以在本地,通过ssh对服务器进行连接,其中也包括上传下载服务。本地打开终端后输入:scp -r djangoName quinns@47.98.212.01:/home/quinns/wwwroot 复制代码将当前目录的djangoName文件夹通过quinns账户上传到/home/quinns/wwwroot目录内。回车执行后输入quinns的密码即可看到上传到指定的wwwroot目录内。六、配置django本地开发环境下的django和服务器的设置有些许不一样。首先要开放ALLOWED_HOSTS,使得程序可以远程访问,然后再设置静态文件,最后再通过命令来测试是否可以顺利启动。开启drf远程访问及静态设置找到django项目的settings.py文件,里面有个ALLOWED_HOSTS,是接收一个空列表,现在要将服务器地址或者域名添加进去(也可以放*号,代表所有都可以指向这里,但是不推荐这么做):ALLOWED_HOSTS = ['47.98.209.107'] 复制代码上面就算是开启了远程访问,接着设置静态(drf有一些样式,如果不设置,通过uwsgi启动是无法加载的)。同样是在settings.py文件中,下部分代码中有个STATIC_URL = '/static/',在它下面新增一行:STATIC_ROOT = os.path.join(BASE_DIR, "static/") 复制代码保存文件,然后在虚拟环境下执行命令:python manage.py collectstatic 复制代码这样django就会收集静态文件,放到指定目录内,也就是(static目录内)七、编写uwsgi配置uwsgi可以通过命令来启动django项目,也可以通过配置文件ini或者xml来启动。这里已ini为例。在项目根目录(manage.py同目录,其实哪个目录都可以,这里是方便寻找)新建文件夹conf,然后再在conf下新建uwsgi文件夹(这俩文件夹什么名字无所谓)。接着新建uwsgi的配置文件,这里暂且叫做lagou_uwsgi.ini 里面写上uwsgi与项目的配置信息:ite_uwsgi.ini file` [uwsgi] # Django-related settings # the base directory (full path) chdir = /home/quinns/wwwroot/GamesAPI # Django's wsgi file module = GamesAPI.wsgi # the virtualenv (full path) # process-related settings # master master = true # maximum number of worker processes processes = 4 threads = 2 # the socket (use the full path to be safe socket = 127.0.0.1:8001 # ... with appropriate permissions - may be needed # chmod-socket = 664 # clear environment on exit vacuum = true virtualenv = /home/quinns/anaconda3/envs/envgames python-autoreload=1 logto = /home/quinns/wwwroot/GamesAPI/uwsgilog.log stats = %(chdir)/conf/uwsgi/uwsgi.status pidfile = %(chdir)/conf/uwsgi/uwsgi.pid 复制代码具体的含义在uwsgi文档都有,这里记录一下:chdir # 项目绝对路径 module # 项目内的uwsgi.py文件,其实与项目同名即可 master processes threads socket # 服务启动地址及端口 vacuum virtualenv # 这个就很重要了,python虚拟环境地址 python-autoreload=1 # python自启动 logto # 自动生成日志文件及存放路径 stats pidfile 复制代码这就算是编写好uwsgi的配置文件了,接着编写nginx的配置。八、单项目nginx配置最好不要改动原有的ningx,来新建一个新的.conf配置文件吧。同样在项目目录的conf目录内新建nginx文件夹,然后再在nginx文件夹里新建lagou.conf配置文件,里面写上nginx的配置:upstream games { # server unix:///path/to/your/mysite/mysite.sock; # for a file socket server 127.0.0.1:8001; # uwsgi的端口 } # configuration of the server error_log /home/quinns/wwwroot/nginxerror.log;#错误日志 server { # the port your site will be served on listen 8080; # 端口 server_name 47.98.209.107 ; # 服务器ip或者域名 charset utf-8; # max upload size client_max_body_size 75M; # adjust to taste # Django media location /media { alias /home/quinns/wwwroot/GamesAPI/media; # 指向django的media目录 } # Django static location /static { alias /home/quinns/wwwroot/GamesAPI/static; # 指向django的static目录 } # Finally, send all non-media requests to the Django server. location / { uwsgi_pass games; include uwsgi_params; # uwsgi服务 } } 复制代码里面都有说明了,我就不写了。其的upstream games中的games是自定义名称,但是要与下面的uwsgi_pass games中games名称相同。注意: .conf文件建立好后,要与让nginx知道并承认,所以需要通过软连接来链接到/etc/nginx/conf.d/目录下,如果不知道软连接怎么做,可以把这个文件copy到这个目录下。然后重启服务器sudo service nginx restart 复制代码有些版本的命令是:sudo systemctl restart nginx.service 复制代码如果没有报错,应该就是可以了。如果有报错,没有重启ng服务器,那肯定是配置文件写错了,得去看一下。九、启动项目既然uwsgi也配置好了,django项目的虚拟环境也pip install -r requirements.txt过了,ng的配置文件也写好了。那就可以启动服务了。nginx的服务启停通过linux命令来进行启停sudo service nginx restart/start/stop 复制代码如果之前启动过,就不用重启了。uwsgi启动项目找到刚才编写的lagou_uwsgi.ini配置文件目录,通过命令来启动:uwsgi -i lagou_uwsgi.ini & 复制代码如果没有报错,就代表启动了。就可以在浏览器访问之前.conf配置文件配置的8080端口了。意外的小问题后期部署发现,不同版本的Centos对权限的设定是不一样的。比如:不能在自定义的nginx.conf文件中填写erro_log的配置需要在/etc/nginx/nginx.conf里面将user改成root如果发现502 bad getway,就需要查看uwsgi日志和nginx日志,如果在nginx日志(默认/var/log/nginx/error.log)看到如下提示2018/08/19 21:06:37 [crit] 967#967: *1 connect() to 127.0.0.1:8001 failed (13: Permission denied) while connecting to upstream, client: 192.168.0.103, server: 192.168.0.61, request: "GET / HTTP/1.1", upstream: "uwsgi://127.0.0.1:8001", host: "192.168.0.61:8080" 复制代码就代表是权限方面的问题,经过网上文章搜索,找到原因SeLinux的导致的。解决办法有两种,比较直接的是运行命令:setsebool -P httpd_can_network_connect 1 复制代码后来又产生新的问题,Django的静态文件无法正常加载,nginx返回的是403.解决这种问题的办法是通过配置文件,长期关闭SeLinux,怎么关:《烦人的linux权限问题-SeLinux》十、nginx配置静态后端api没有问题后,前端也要部署。前端通过npm run build打包之后,将build文件通过ssh上传到wwwroot目录下:scp -r build quinns@xx.xx.xx.xx:/home/quinns/wwwroot 复制代码等到上传完成后,就到nginx那里进行静态的部属配置cd /etc/nginx 复制代码然后打开nginx.conf文件进行编辑。首先要给nginx文件进行访问授权,否则有些目录是会报错403的。其配置文件中有:user nginx; 复制代码这里得给他改成用户的权限,如quinns或者rootuser quinns; 复制代码看到server部分的代码:server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { # 配置这个,才能正确跳转路由,如47.98.110.67/detail/1 add_header Cache-Control "no-cache, no-store"; index index.html; try_files $uri /index.html; } …… …… } 复制代码是这样的,访问网址80端口默认指向/usr/share/nginx/html目录下的index.html因为静态打包后build也是由index.html来作为主入口的。所以这里只需要把root的指向改过来即可:server { listen 80 default_server; listen [::]:80 default_server; server_name _; #root /usr/share/nginx/html; root /home/quinns/wwwroot/build; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { # 配置这个,才能正确跳转路由,如47.98.110.67/detail/1 add_header Cache-Control "no-cache, no-store"; index index.html; try_files $uri /index.html; } …… …… } 复制代码将原来的root指向注释掉,增加build文件夹的指向然后重启nginx服务,打开浏览器访问,就可以看正常的页面了。心中一阵窃喜,这个坑终于是填上了。文章来源:掘金社区作者:云享专家韦世东
-
JavaScript 渗透的范围越来越广,它能做的事情已经远不止前端开发而已。不久前stateofjs.com刚刚发布了 2017 JavaScript 现状报告 ,现在Ryan Chartrand非常应景地推出了 2018年的JavaScript发展趋势 ,把这两份文章一起结合来看,相信作为JS开发者的你一定不再迷茫。 去年,有50000人对JavaScript的 上升趋势 感到好奇。。那么好吧,我的开发者同胞们,现在我们再来看看2018年怎样。 如果你2017年一整年都与世隔绝或者忙于项目而自顾不暇的话,这篇文章就是给你准备的。 2017年发生的很多事情正在为2018年的许多行动和创新做好准备。 你还可以把本文用作规划个人成长的指南,来推出更具创新性的项目 。 React vs. Vue.js 我们开门见山,直接上好东西吧:认为 Vue 可能会成为 React 的一大竞争敌手的人不是很多,但是今年想要无视Vue是不可能的,在开发者的炒作方面甚至令Angular黯然失色。 展望2018年的时候,我们即将迎来2年的激烈竞争,而对Vue的炒作会非常多。 React有着全球最富有公司之一的财政支持,更不用说他们还有极其有才的维护人员。 但是Vue做了下面这些让开发者真心高兴的事: Vue轻量,容易学习,有着令人难以置信的工具,有很棒的状态管理和路由内置(!)等等。 Vue的社区当然还没有React那么大,但从核心团队是否有很好的使者并且是否倾听客户来看,这个社区正在壮大。 谈到取悦开发者,不要忘了Facebook今年在BSD+Patents的收钱事件中遭遇的史诗般的失败,这惹恼了不少的开发者。 我们现在其实已经发展到你一样可以通过Vue启动任何项目来让自己生活变得简单的程度,这是他们的核心团队取得的一项令人印象深刻的成就。 这里有一篇文章对React、Vue和Angular进行了 很好的对比: https://medium.com/unicorn-supplies/angular-vs-react-vs-vue-a-2017-comparison-c5c52d620176 最终可能会发展成什么样子?Facebook会做它最擅长的东西:抄袭创新者然后世界继续使用React。 如果你是一位拒绝学习React的Angular开发者,随着Angular的机会日渐消亡,Vue应该迅速成为你职业生涯更好的选项(而且Vue的部分语法跟Angular类似)。 每一位JS开发者都仍然应该考虑学习Vue.js,因为它已经制造了足够大的水花,现在我们开始看到对Vue.js开发者的需求出现,这意味着至少在短期内你能够为自己创造一些价值。 Next.js 然后就是 Next.js ,我们称之为“React的保险”。 尽管Facebook从未在这场游戏中领先过,并且在创新的势头上无法与Vue匹敌,但在工具使用、代码切割、路由以及状态管理方面, React加上Next.js能让你在体验上跟Vue接近许多 ,此外还能让你获得大规模的React生态体系和支持。 Next对于用React开发的server-side为主的应用也特别有用,二者在应用已经日益成为趋势。 此外,再加上 Now.js (由同一支团队开发)你就能得到超级快速的部署React应用的方式。 随着Vue与React之争的延续,预计会看到越来越多Next与React的双剑合璧,这会让React阵营的每个人都感觉舒服一点。 Angular 尽管Angular越来越难以取悦开发者,它仍将是2018年广受采用的框架之一。 许多公司采用了Angular 1.0,随着2018-19年间他们寻求移植到更好的框架,这些会关注React或者Vue会不会是Angular 2更好的替代。 Angular剩下的拥护者已经表态说Angular会成为企业选择的JS框架,但这一断言尚有待证实,而且在2018年未必能得到证实。 这里有你需要了解的2017年 有关Angular的一切: https://medium.com/@chriscordle/why-angular-2-4-is-too-little-too-late-ea86d7fa0bae Reason Facebook生产使用的一切永远都值得关注。 Facebook现在用 Reason 来开发 web版的Facebook Messenger 以及其他项目(Whatsapp、Instagram、Ads等)。 2017年,他们还推出了 reason-react ,将reason跟Reacy绑定在一起,这样你就可以写出可编译成惯用ReactJS的 Reason代码。 所以你可想象一下,不需要安装Babel(+许多插件)、Flow等,只需要OCaml + Reason(现在已经支持React绑定)即可的强大。这是一个有待开发的一大趋势。 在JS领域Reason今年获得的注意力要比大部分的编译成js型语言多很多,所以这绝对是2018年值得继续关注的趋势之一。 GraphQL GraphQL 是API的查询语言(可以看成是REST的现代版)。 GraphQL没有辜负2017年的炒作,像 Yelp、Spotify、Github、沃尔玛、《纽约时报》 等主流公司都在采用GraphQL,而且现在还有了基于GraphQL的API。 其中一些API甚至是专门支持GraphQL的,甚至连REST选项都没有。简而言之: 创新公司正在押注到GraphQL身上 。RESTful API当然还远没到灭亡的地步,但再次地,看看初创企业的使用趋势就知道GraphQL是个热门选项。 另一方面,像Falcor这样的替代者几乎连讨论的声音都没有了。 如果你想在创新公司找份工作的话,现在绝对是开始学习GraphQL的时候。 Redux、Relay Modern 以及 Apollo Redux 是一直很火的 Dan Abramov / Andrew Clark 项目,后来有迅速成为React状态管理和数据抓取的首选解决方案。 但GraphQL改变了现状,主要是在数据抓取方面。 我们现在有了 Relay Modern (Facebook开发)和Apollo,让你可以比Redux更高效地抓取并传递GraphQL数据到React应用的GraphQL客户端框架。 但就像一位开发者指出那样,Relay/Apollo/Redux: 这些框架和它们的好处未必需要是互斥的。实际上,这几个一起使用可以提供很好的关注分离,这是web开发来说可太重要了。 这意味着你仍然可以跟Relay一起用Redux,Redux用于本地状态管理以及一些复杂的非服务器状态,然后由Relay来抓取。 但是社区对简化这个的东西(目前为止唯一的答案是Vue.js或者 Cashay )比较焦虑。 Storybook 哇哦,2017年Storybook 真的是火了 。 Storybook是一个定义、开发和测试UI组件的环境。 它从年初的几乎一潭死水变成年中的大规模流行,这要感谢社区的巨大努力。这是一个极其激励人的故事,展现出了开源工作应该如何演进,它的故事真的值得一读: https://medium.com/storybookjs/the-storybook-story-dd3c1ab0d2ce Storybook太有用了(而且用起来也很有趣),你可以单独地开放和测试UI。它就像一本实时的UI设计指南,能够为开发者提供真正的价值。 你可能已经碰到过用Storybook来设计的开源项目了,但如果想自己看看它是怎么用的话,那就看看用Storybook设计的 Airbnb的日期选择器 。 作为开发者如果你想在2018年脱颖而出,那就在面试的时候用Storybook展示你的最新项目。 额外提示:建议你也看看 react-bluekit ,这是Storybook的替代,Netflix的工程团队用来设计他们的组件库。 Prettier 就像它的名字一样,Prettier是一个代码格式化程序,可以让你的代码可读性更强,并且很好看。 它在GitHub上面得到了18000颗星 ,开发者都很喜欢这个简单又有价值的项目。 它还被用到了许多其他你热爱的项目上,比如Webpack、React、Next.js、Babel等。 再次地,你可以成为一名Go开发者,这样就不用安装这个也能享受它的功能了。 Jest和Enzyme 说到JavaScript测试, Jest 无疑是领先的那个,而 Enzyme 则是很好的补充,尤其是在开发React应用的时候。 就像你 在这里看到一样 ,在下载方面Jest现在统治着Jasmine。 Jest的Snaps**s功能在2017年真的起来了,使得处理测试的痛苦少量很多。你可以看看React Conf 2017的这次 演讲 来了解它的一切。 由Airbnb工程团队开发的Enzyme是一个测试React组件的JavaScript库。自从2016年以来它已经在GitHub上面拿到了12000颗星。 Jest和Snaps**s + Enzyme超级简单的React组件测试API形成了一个很强的测试组合,会在2018年不断流行起来。 Webpack Webpack 已经崛起为最流行的资产打包工具。Webpack还经历了不可思议的一年。 去年的这个时候,Webpack还几乎连文档都没有,大部分的开发者都不知道该怎么开始用它。 然后,到了今年年头的时候,我写了篇文章,说Webpack在3个月内就拿到了15000美元来支撑这个项目是如何的不可思议。而他们现在已经拿到了几十万美元的融资了。 Webpack不仅为开源项目如何走向繁荣铺好未来,而且这个项目一整年都维持了很旺盛的发展势头。 谢天谢地,Sean Larkin还在领导着Webpack,所以Webpack能够取得下一个成就还没有结束的迹象。 因为这该项目得到的支持如此之好(以及他们对社区的关心程度如此之高),所以2018年最好预测的趋势就是它了。 Parcel 但每一个大规模的趋势发展的同时,也会有不满开发者队伍的日益壮大。 Parcel,一个有竞争力的打包工具,也相当迅速地获得了发展势头,目前它已经在GitHub上面攒到了12000颗星,开始直接威胁到Webpack的江湖地位。 Parcel的卖点是别的打包工具都变得太庞大了,而Parcel的打包速度是Webpack的2倍(使用缓存的时候快10倍)。 它还针对Webpack多少有点令人困惑的配置设置,而Parcel却不需要配置。 只用把你应用的入口点指给它,它就会把事情做对了。 尽管简化配置和改善速度都是很好的改进,如果2018年上半年Webpack没有抄这些改进的话我不会感到奇怪。 类似于Vue与React之争,这个小家伙总是创新得更快,但要取决于大家伙会不会受到哪些创新的灵感启发而改进自己的项目。
-
本文来源知乎网由于我们为我们的客户创造开源软件、开发很多应用并改进他们的质量,我们一直在寻找能提高我们所开发的软件质量的东西。部分工作是在盯着一些让我们眼花的提议和新兴的标准,从而找到高效可用的部分。在这里,我们将探索已经开始使用的或者在将来工作中考虑用到的五个新兴的web标准。CSS变量/自定义属性在过去的十多年里,web工程师一直在用变量来创建和管理复杂的CSS系统,他们仍然是驱动对CSS预处理器产生需求的主要特性的一种,像Sass、less、Stylus。用得好的话,通过规范所有用来描述颜色、文字、边距等的值,他们能极大地增强大型代码库的可维护性。随着时间的推移,预处理器变量已经随着设计或习惯融合了一系列可共享的特性。 [*]有前缀:例如,为了防止与已有的CSS关键词冲突的 $或 @ [*]有作用域:在.container中可以用.container > .child访问$bgColor,但反过来就不成立了。 [*]可以被重写: $bgColor: blue;.container { $bgColor: red; background-color: $bgColor; /* will be red */} 原生的CSS变量(或“自定义属性”)采用了所有这些约定,这使得转换成原生的支持简单直观。CSS变量必须以两个破折号为前缀:--,他们的作用域为定义的选择器内,可以被后代继承,可能在后代中被重写。例如::root { --bgColor: periwinkle;}.container { background-color: var(--bgColor); /* will be periwinkle */}.container .child { --bgColor: lime; background-color: var(--bgColor); /* will be lime */} 在这个例子里,CSS变量和预处理器变量之间有一些明显的差异: [*]CSS变量在使用的时候必须被var()包裹 [*]--前缀与已有的预处理器使用的任何其他前缀都不一样,因此能一起使用。 [*]CSS变量必须在选择器内定义,所以最接近“全局”作用域是:root [*]预处理器不知道DOM,因此靠嵌套来实现继承。CSS变量的值继承自DOM树,这一点跟普通CSS变量的继承方式一样。 预处理器变量和CSS变量之间另一个显著差异在上文的代码中并不明显:因为它们没有被编译成静态值,CSS变量可能在浏览器运行的时候被更新。这意味着CSS变量可以在Javascript代码里读写,用来做计算或是动画。以下的Codepen示例示范了怎么样用CSS变量和Javascript来创建手风琴动画。codepen例子CSS变量能被 Firefox, Chrome, 和 Safari支持,当前版本的Edge部分支持: http://caniuse.com/#feat=css-variablesCSS模块化变量并不是唯一一种Javascript渗透到CSS的概念。特别是在近几年,Javascript开发者已经把目光转向CSS组织,在Atwood’s Law(阿特伍德定律,即:任何可以用JavaScript来写的应用,最终都将用JavaScript来写)的延伸上思考着“我能做的更好”。这里有一些好的理由可以证明: [*]不像JavaScript,CSS类名在全局的命名空间一直存在 [*]在大型的编译过的样式中解决冲突是靠不住的,并且容易产生意想不到的行为,或是仅仅通过增加权重解决 [*]样式透过DOM层级,能够用出人意料的方式改变子元素的样式 [*]用Javascript来管理CSS允许CSS规则以运行逻辑为基础 React在这种思路上尤其前卫,被Javascript控制的行内样式可以当做这些问题的答案。(可能不是样式渗透子元素,但是确实减少了一定数量的这种样式冲突的案例)。然而,它也有自己的一些缺点: [*]伪类(如: :hover或 :focus)在CSS很容易实现,但在Javascript必须被伪造 [*]在Javascript中重新实现媒体查询是劳动密集工作 [*]内联样式失去了被更高权重覆盖的能力,因为它们已经处于权重层次结构的顶端。 [*]在动态样式上切换类名、Css变量、函数(像 calc())已经解决了大多数难题 [*]性能:DOM数量是一方面,CSS能被缓存 CSS模块化,在某些方面来说是CSS开发者回归到被Javascript开发者侵入的地盘:是一种解决批评和改进样式表而不会完全消除它们的方法。CSS模块本质上归结为可以引入JavaScript的局部作用域的CSS文件,并编译成唯一的类名。例如,这里:JavaScript:import * as css from ‘css/buttonComponent.css’;const buttonHTML = `${buttonText}`; CSS:.root { background-color: #ffffff; color: blue; border: 1px solid blue;} 将会被编译成以下内容:HTML:="buttonComponent_root_**2718"[/color>Button with modular CSS CSS:.root { background-color: #ffffff; color: blue; border: 1px solid blue;} 因为 buttonComponent.css里的类是局部作用域的,并且在一个命名清晰的CSS文件中,所以就不再需要特定的类名了例如 .button。相反,推荐的格式是使用单个标准化的“根”类名称,如 .root或 .normal,然后使用特定状态的类名,如 .error, .success或 .disabled,所有这些类名可能在一定条件下应用于JavaScript 。CSS模块化还通过弱化写多个类名并使用composes解决样式容易覆盖的问题。composes关键字类似于Sass的 @extends的预处理器装饰器,除了在CSS中编译样式之外,composes以可预见的顺序返回多个命名空间的类名。例如:CSS:.root { display: inline-block; padding: 10px; color: blue;}.success { composes: root; color: green;} JavaScript:import * as css from 'css/buttonComponent.css';// css.success = 'buttonComponent_root_**2718 buttonComponent_success_pi3141'const buttonHTML = `${buttonText}`; HTML:="buttonComponent_root_**2718 buttonComponent_success_pi3141"[/color>Button with green text CSS也有一种更强的工具来增强样式的模块化: all关键词,与CSS模块化区分,能用来重置所有的属性到最原始的状态,例如.root { all: initial; }。因为这是一个新的CSS属性而不是依赖于webpack或Browserify编译器的类型,IE和Edge仍然缺少支持度。Async/Await/能让你的代码变得很棒随着越来越多的成功和偶尔(捕获)的错误,JavaScript用Promise来改进异步代码的处理。在promise出现以前,一个回调可能看起来像下面的嵌套末日: doAsyncFunction(function(result) { doSecondAsyncFunction(result, function(resultTwo) { doThirdAsyncFunction(resultTwo, function(resultThree) { // and so on… }, catchError); }, catchError);}, catchError); 以上代码用promise能被转换成链式顺序,用.then()完成请求回调,用.catch()结束:doAsyncFunction() .then(result => doSecondAsyncFunction(result)) .then(resultTwo => doThirdAsyncFunction(resultTwo)) // and so on… .catch(catchError); 跟早期金字塔形的传入回调和错误处理相比,这种链式语法显然更干净清晰,更容易理解。使用 async函数,开发人员不必等到写异步代码像同步代码一样直观的那天。使用async / await的初始示例如下所示:(async function() { try { const result = await doAsyncFunction(); const resultTwo = await doSecondAsyncFunction(result); const resultThree = await doThirdAsyncFunction(resultTwo); return resultThree; } catch(error) { catchError(error); }})(); 每个await将会中断代码的执行,直到它的promise成功回调,整段代码被包含在try/catch块内,就像同步代码一样。要记住的最重要的点:await可能只用在async 方法里作为特定的关键词,async方法本身还是异步的,不会阻塞周围的代码运行。这个三个promise的例子显示三个promise进程如何能够被逐个执行,Promise.all和Promise.race能与async/await同时运行:async function doAsyncStuff() { const [resultOne, resultTwo, resultThree] = await Promise.all([ doAsyncFunction(), doSecondAsyncFunction(), doThirdAsyncFunction() ]); // do stuff with resultOne, resultTwo, and resultThree} 在技术上,await甚至不需要通过promise,因为它将在Promise.resolve中包含任何非promise的值。任何promise解决方案都会被push到调用堆栈的末尾,这个事实可能会导致一些稍微奇怪但有趣的结果: async function getAnswer() { const answer = await 42; console.log(`The answer to life, the universe, and everything is ${answer}`);}getAnswer();console.log(‘Vogons blow up Earth’);// will log:// “Vogons blow up Earth”// The answer to life, the universe, and everything is 42 阻塞元素 / 惰性元素对于任何需要创建模态框并关注可访问性的开发人员来说,管理焦点一直是并且仍然是一个难以解决的问题。焦点应该从不被放到隐藏或是挡住的DOM元素,但做出合适的行为是痛苦而密集的。两个基本的监听聚焦事件选项,当人们尝试离开模态框,或是通过设置tabindex="-1"来实现手动移除所有聚焦中的非模态框元素。这两种解决方法通常以使用大型而脆弱的DOM查询来在代码里监听焦点元素结束。document.querySelectorAll('a[href], button:not([disabled]), area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]); 即使焦点已经被处理,隐藏的部分应该将aria-hidden设置为true,因此它不会被屏幕阅读器等辅助技术所读取。两个规范提案建议将从整体上解决模态问题:inert和blockingElements。首先,HTML属性inert,将会从关注的顺序里移除一段DOM树(好像所有可聚焦的元素都接受tabindex="-1"),并从辅助技术隐藏。blockingElements将几乎完全相反:暴露一堆“blocking elements”,这将有效地使所有其他DOM树变得惰性。例如,如果我有以下DOM结构:="content"[/color>="modal"[/color> Modal contentcolor> Other content, including linksbuttons/etc="sidebar"[/color>为了打开这个对话框,我将移除inert属性,并调用document.$blockingElements.push(document.querySelector('.modal')),这将渲染成不仅仅是直接的兄弟树inert,而且是父级和组先级的兄弟inert。inert和blockingElements仍然是提案,所以在任何当前的浏览器中都不是本地支持的。然而,有可用的polyfills可以使用它们现在使用: https://github.com/WICG/inert 和 https://github.com/PolymerLabs/blockingElements交叉观察器观察元素滚动到视口内一直都是滚动插件开发的内容,这些插件使用一些滚动事件监听(希望能节流)。现在,交叉观察器允许开发者用一些选项和回调来创建一个观察器来观察元素滚动到视口内,这仅仅用vanilla JavaScript就可以实现。IntersectionObserver与其他DOM观察很像(像MutationObserver),在这里,你可以用一个回调和选项来创建,然后在一个DOM元素上调用.observe。每当元素与视口的交叉距离增加10%时,简单实现更新可以如下所示:const observerOptions = { root: null, rootMargin: '0px' threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]};function intersectionCallback(entries) { entries.forEach((entry) => { const percent = Math.floor(entry.intersectionRatio * 100); console.log(`Element ${entry.target} scrolled ${percent}% into view`); });}const observer = new IntersectionObserver(intersectionCallback, observerOptions);observer.observe(myElement); 对象由以下属性组成: [*]root:包含滚动区域的元素(如果空着,默认为文档的视口viewport) [*]rootMargin:在根元素周围能够增加或收缩,用来计算临界点 [*]threshold:一组数字格式,表示回调应该触发的目标元素的可见性的百分比。例如[0,0.5,1],将在元素滚动过0%,50%、100%可见区域时触发。 为了停止观察特定元素——如果回调仅仅在元素第一次滚动的时候才需要,或是,它需要被用来观察大量元素滚动进入或退出视窗,只需要调用observer.unobserve(myElement);。断开整个观察,执行observer.disconnect();。在IntersectionObserver,一个使用unobserve()的好的案例是懒加载图片脚本,这个脚本在滚屏时用到。 function onImageIntersect(entries) { entries.forEach(entry => { entry.target.src = imageSource; observer.unobserve(entry.target); }}document.querySelectorAll('img').forEach(img => observer.observe(img)); 浏览器仍然在慢慢渐渐给出支持,但它不用polyfill就已经可以用在Chrome, Firefox和 Edge了。IE和Safari现在还缺少原生的支持。
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签