2018-02-26 21:53:32 dounine 阅读数 2071
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57505 人正在学习 去看看 李宁

React官方介绍可编辑Div的资料比较少,方式二在所有的资料中都没找到,是通过特殊的方法实现的。

dangerouslySetInnerHTML 是React专用的属性

<div id="context" contentEditable={true} dangerouslySetInnerHTML={{__html: this.state.inputValueHtml}}></div>

获取焦点方式1

document.querySelector('#context').focus();

PS:以上方法只会在句首获取焦点

获取焦点方式2

let srcObj = document.querySelector('#context');
let selection = window.getSelection();
let range = document.createRange();
range.selectNodeContents(srcObj);
selection.removeAllRanges();
selection.addRange(range);
range.setStart(srcObj, 1);
range.setEnd(srcObj, 1);

以上方式会在最后句尾获取焦点

2019-08-23 21:10:57 u011625768 阅读数 275
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57505 人正在学习 去看看 李宁

最近项目中h5端要实现图文上传,而且还要支持用户用户输入的格式,例如换行啥的,那么使用输入控件保存输入内容,图片上传控件就不合适了,因为很难知道用户的输入样式。

如果使用一些现有的富文本编辑器,貌似又不是很划算,所以综合考虑使用div来自己实现一个就是比较理想的方案了。

先来考虑一下,如果使用div来实现简单的富文本编辑器,需要解决哪些问题?

首先,div默认是不可编辑的,需要设置它可以编辑,这个很简单只需要使用

   contentEditable="true"

其次,我们需要在div指定的位置插入图片,指定位置就是光标所在的位置,那么我们插入图片的时候,需要知道光标的位置,网上查了下应该是需要getSelection API W3Cschool上面直接有简单的用法,感兴趣的童鞋可以去看看。

获取当前光标选中位置

let selection = window.getSelection();
let range = selection.getRangeAt(0);

那么知道了光标位置,怎么插入图片?

 let img = document.createElement("img");
   img.src = json.url;
   //最大宽度为手机宽度减去20
   img.style.maxWidth = (width-20) + 'px';
   range.insertNode(img);//选中位置插入图片   

这就完了?

当然还没完,实现中遇到了另一个问题,那就是焦点的变化。如果我想插入图片,我就首先需要上传图片,但是当我点击上传图片的按钮的时候,焦点就已经发生了变化,这个时候获取的选中位置就是你点击的位置!

那么该如何解决这个问题呢?

仔细分析,点击的时候,焦点变化的时候,我们需要记录下上次焦点的位置,上次焦点的位置在哪里?在可编辑的div中,焦点变化的时候,就是可编辑div失去焦点的时候!

那么是否可以监听div失焦这个事件呢?答案当然是的!

 onBlur={() => {
   let selection = window.getSelection();
   range = selection.getRangeAt(0);
   }}

给div实现onBlur监听就可以了,是不是很简单?

接下来看一下完整的代码

import React, {Component} from "react";
import "../../assets/css/topic/DivEdit.css";
import "../../assets/common/second-common.css";
​
let range;
let width = document.documentElement.clientWidth;
//头部栏
class DivEdit extends Component {
    constructor(props) {
        super(props);
        let typeArr = [".png", ".jpg", ".jpeg", ".bmp", ".gif"];
        this.state = {
            id:'',
            inputValueHtml: '',
            fileType: typeArr,
            showTips:true
        }
    }
​
​
    //光标定位在可编辑div的开始的位置
    setStartFocus() {
​
        document.querySelector('#my-question-define-edit').focus();
​
    }
​
​
    //光标定位在内容的尾端
    setEndFocus() {
​
​
        let srcObj = document.querySelector('#my-question-define-edit');
        let selection = window.getSelection();
        range = document.createRange();
        range.selectNodeContents(srcObj);
        selection.removeAllRanges();
        selection.addRange(range);
        range.setStart(srcObj, 1);
        range.setEnd(srcObj, 1);
​
    }
​
​
    // 调用input 选择文件
    triggerSelect() {
        return document.getElementById('topic-add-img').click();
    }
​
​
    //处理上传文件
    handleChange(file) {
        // 判断文件类型进行上传
        if (window.typeMatch(this.state.fileType, file.name)) {
            // 上传文件
            this.saveFile(file);
        } else {
            Toast.fail("文件类型不符,请重新选择", 3);
        }
    }
​
    // 上传文件
    saveFile(file) {
     
     //你们自己处理上传的处理 //当成功的时候
    
             let img = document.createElement("img");
             img.src = json.url;
             img.style.maxWidth = (width-20) + 'px';
​
            if(range) {
​
                  range.insertNode(img);
​
           }else{
​
                this.setStartFocus();
                let selection = window.getSelection();
                range = selection.getRangeAt(0);
                range.insertNode(img);
​
              }
   
    }
​
    componentDidMount() {
        let question=this.props.question;
        this.setState({
        inputValueHtml: '<p>' + question + '</p><br/><br/><br/>',showTips:!(question&&question!=='')
        }, 
        () => {
​
            if(question&&question!==''){
​
                this.setEndFocus();
​
            }
        });
​
    }
​
​
    getHtml(){
​
        return this._editDiv.innerHTML;
​
    }
​
​
    render() {
​
​
        return (
​
            <div className={'add-my-question-first-edit'} >
​
                <div id={'my-question-define-edit'}
                     onFocus={() => {
​
                         this.setState({showTips:false});
​
                     }}
                     onBlur={() => {
                         let selection = window.getSelection();
                         range = selection.getRangeAt(0);
                     }}
                     onClick={() => {
​
                         let selection = window.getSelection();
                         range = selection.getRangeAt(0);
​
                     }}
                     ref={(editDiv) => this._editDiv = editDiv}
                     className={'add-my-question-edit'}
                     contentEditable="true"
                     dangerouslySetInnerHTML={{__html: this.state.inputValueHtml}}/>
​
                {
​
                    this.state.showTips?
                        <span style={{
                        position: 'absolute',
                        top:10,
                        left:10,
                        marginTop:'1.333rem',
                        fontSize:17,color:'#E8E8E8'
                        }}>输入您的问题...</span>:null
​
                }
​
                <div className={'add-bottom-menu'}>
​
                    <img style={{width: 20, position: 'absolute', left: 10}}
                         src={require("../../assets/images/topic_topic_show.png")}
                         alt='箭头'/>
​
​
                    <div className={'topic-add-pic'}
                         onClick={() => {
                              //调起上传图片
                             this.triggerSelect();
                             
                         }}>
                        <img style={{width: 20}}
                             src={require("../../assets/images/topic_upload_pic.png")}
                             alt='上传图片'/>
​
                        <span style={{marginLeft: 10, fontSize: 15}}>上传图片</span>
                    </div>
​
​
                </div>
                
                <div style={{visibility: 'hidden'}}>
                    <input
                        type="file"
                        id={'topic-add-img'}
                        style={{display: "none"}}
                        onChange={e => this.handleChange(e.target.files[0])}
                    />
                </div>
​
            </div>
​
        );
    }
}
​
export default DivEdit;​

样式表

.add-my-question-first-edit {
    display: flex;
    flex-direction: column;
    flex: 1;
​
 }
​
.add-my-question-first-edit  .add-my-question-edit{
​
    flex: 1;
    margin-top: 1.333rem;
    /*margin-bottom: 1.333rem;*/
    font-size: 0.453rem;
    background-color: #fff;
    padding: 10px 10px 1.867rem;
​
}
​
.add-my-question-first-edit  .add-bottom-menu{
​
    position: fixed;
    bottom: 0;
    width: 100%;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    height: 1.067rem;
    background-color: #fff;
    border-top: #E8E8E8 solid 1px;
​
​
​
}
​
.add-my-question-first-edit  .add-bottom-menu .topic-add-pic{
​
    display: flex;
    flex-direction: row;
    align-items: center;
    align-self: center;
​
}

更多内容,欢迎同步关注作者公众号二维码!
程序员内功修炼手册 主要发布计算机基础、设计模式、计算机网络基础知识,同时重点关注大前端知识
Android、iOS、web前端、Flutter、React Native等,想学习大前端知识的速度来吧,一起学习、一起成长!
qrcode_for_gh_f730c342ff6e_344.jpg

2018-12-18 21:44:59 qq_33781658 阅读数 140
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57505 人正在学习 去看看 李宁




react列表编辑与更新


<body>
<script type="text/babel">


class Todolist extends React.Component{

render(){
	return(
	<div>
	...
	</div>	
	);
}

}

ReactDOM.render(<Todolist />,document.getElementById('root'));


</scritp>
</body>




<ul id="list" className="list>
{
	aList.map(function(){
	<li key={index}>
	<span>{item}</span>
	<a href="javascript:;" className="up">上</a>
	<a href="javascript:;" className="down">下</a>
	<a href="javascript:;" className="delete">删除</a>
	</li>
	})
}
</ul>





再修改一下class

class Todolist extends React.Component{

constructor(props){
	super(props);
	this.state={
	aList:['abc01','abc02','abc03']
	}
}

render(){
	return(
	<div>
	...
	</div>	
	);
}

}



 

2019-05-06 15:12:33 hhy1006894859 阅读数 472
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57505 人正在学习 去看看 李宁

1、安装

yarn add wangeditor --save-dev

2、引入并使用

代码:

import E from 'wangeditor'

 

<div className="text-area" >
                        <div ref="editorElemMenu" className="editorElem-menu"></div>
                        <div ref="editorElemBody" className="editorElem-body"></div>
                    </div>
 componentDidMount() {
        const elemMenu = this.refs.editorElemMenu;
        const elemBody = this.refs.editorElemBody;
        const editor = new E(elemMenu,elemBody)
        // 使用 onchange 函数监听内容的变化,并实时更新到 state 中
        editor.customConfig.onchange = html => {
            console.log(editor.txt.text())
            this.setState({
                // editorContent: editor.txt.text()
                editorContent: editor.txt.html()
            })
        }
        editor.customConfig.menus = []
        editor.customConfig.uploadImgShowBase64 = true
        editor.create()
    }

下图是代码截图:

3、注意事项

由于我的项目使用的是electron打包,项目直接在浏览器中使用的时候,以上代码已经支持输入法中 的一些emoji表情显示,但是在electron打包下不能用,原因:

loginWindow.loadURL('http://localhost:3000/index.html?sjs='+ new Date().getTime() +'/#/')

主窗口打开的是服务器上的网页,导致输入法打的表情不能正常显示。

解决方案:

 loginWindow.loadURL(url.format({
        pathname: path.join(__dirname, './build/index.html'),
        protocol: 'file:',
        slashes: true
    }))

对开发调试好的文件,进行文件打包。

若是单纯的打包,是不能运行的,需要将静态文件变为相对路径:

package.json文件中:

新增

"homepage": ".",

此时,再执行npm run build,打包好的文件可以直接执行。

2019-02-01 16:53:14 Napoleonxxx 阅读数 1647
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57505 人正在学习 去看看 李宁

在这里插入图片描述

前言

之前我们了解了Fiber的数据结构 React 16.7 Fiber源码解读 (一)之数据结构

接下来主要探讨fiber运行的流程。

目录如下:

  • Fiber vs Stack
  • 关键字Work
  • 运行流程图
  • 重要函数源码解读
    • enqueueSetState
    • requestWork
    • scheduleCallbackWithExpirationTime
    • workLoop
    • performUnitOfWork
    • beginWork
    • performUnitOfWork动态图
    • commitLifeCycles
  • Fiber Debugger

fiber vs stack

上一篇我们介绍了fiber的数据结构,自然产生出几个问题:

  1. 为什么React 16之前的stack reconciliation无法被中断?
  2. 为什么fiber的数据结构可以使进行中的work可以被中断?

我打算从stack和fiber数据结构的角度来回答这个问题:

stack无法被中断是因为中断后无法恢复现场,fiber可以被中断是因为中断后可以恢复现场。 听起来像句废话,容我接着分析:

首先来看stack:

// stack node数据结构
export type Node = {
 ...
 children: [...]
 ...
}

其实是一个树结构,每个node维护一个children保持对子节点的引用。
通过walk来构建虚拟dom:

// stack reconciliation通过递归调用walk生成visual dom
walk(root);

function walk(instance) {
    doWork(instance);
    const children = instance.render();
    children.forEach(walk);
}

function doWork(o) {
    console.log(o);
}

我们都知道,递归调用的过程是一层层的入栈,每一层我们称为stack frame,当一个stack frame完成后出栈回到上一级。假设某个子节点还在运行时,由于出现了优先级更高的任务,导致整个walk运行现场被打断了(之前的多层入栈都被清除了),尽管我们也许还保有被中断节点的引用,当高优先级任务运行完成,只凭这个中断节点的引用是无法恢复递归的现场的,我们既无法找到它的兄弟节点,也无法找到它的父亲节点

再来看fiber:

  1. 数据结构给力
  2. 构建visual dom不再使用递归
export type Fiber = {
  // ...
  // 当前fiber的父级fiber实例
  return: Fiber | null,
  // 子Fiber
  child: Fiber | null,
  // 兄弟fiber
  sibling: Fiber | null
  // ...
}

关键字Work

React在协调阶段(reconciliation)的各项工作诸如运行周期函数,更新ref,都可以认为是一个Work.

对于不同类型的React Element所做的work也不尽相同。在React中我所知道的element类型就有class component,function component, host component,portals等。

通过每个fiber,可以看出其所对应的react element有哪些work要做,即一个fiber对应unit of work。fiber的数据结构可以使其对应的work可以被追踪(track),暂停(pause)或者放弃(abort)

运行流程图

先上一张运行流程图:
workflow

重要函数源码解读

接下来会了解运行过程中涉及到的一些重要函数。

enqueueSetState

我们在业务代码中通常用setState来改变组件状态,在调用栈中,我们经常会看到enqueueSetState,代表React接管了之后的工作,即将进入协调(render/reconciliation)阶段

在React 16.7中,enqueueSetState做了以下几件重要的事情:

  1. 建立组件对应的fiber
  2. 将组件的update放入fiber的updateQueue中
  3. 通过scheduleWork正式开始任务调度

ReactFiberClassComponent.js

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
	  // ...(DEV WARNING)
      update.callback = callback;
    }

    flushPassiveEffects();
    // 将组件的新状态更新到该组件对应的fiber.updateQueue中
    // 16.3中为 insertUpdateIntoFiber(fiber, update)
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  }
  ...
}

work的调度工作主要在ReactFiberScheduler.js中完成。流程图我们可以找到一条由重要函数组成的调度链:scheduleWork -> requestWork -> performSyncWork / performAsyncWork -> workLoop -> performUnitOfWork -> render

requestWork

function requestWork(root, expirationTime) {
	...
   if (expirationTime === Sync) {
       performSyncWork();
     } else {
       scheduleCallbackWithExpirationTime(root, expirationTime);
     }
   ...
}

scheduleCallbackWithExpirationTime

function scheduleCallbackWithExpirationTime(root, expirationTime) {
  // ...
  callbackExpirationTime = expirationTime;
  const currentMs = now() - originalStartTimeMs;
  const expirationTimeMs = expirationTimeToMs(expirationTime);
  const timeout = expirationTimeMs - currentMs;
  // 将异步(低优先级)任务放入requestIdleCallback或其polyfill
  callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
}

workLoop

非常简短但是很关键。
循环而不是以递归的方式遍历fiber tree.
isYieldy 为 true, work为异步否则为同步

function workLoop(isYieldy) {
  if (!isYieldy) {
    // Flush work without yielding
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // Flush asynchronous work until there's a higher priority event
    // shouldYieldToRenderer()为true代表没有剩余时间执行异步低优先级任务即nextUnitOfWork
    while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

performUnitOfWork 和 beginWork

performUnitOfWork 调用的核心函数是beginWork,beginWork会遍历当前workInProgress的所有子级fiber,完成单元任务的处理,之后继续处理下一个任务。

   // 执行一个workInprogress并且返回下一个可执行的workInProgress(next),直到next不存在,结束本次workloop
   function performUnitOfWork(workInProgress) {
	    var current = workInProgress.alternate;
		// ...
		
	    // See if beginning this work spawns more work.
	    // 遍历所有子级fiber,完成单元任务的处理,之后继续处理下一个任务
	    var next = beginWork(current, workInProgress, nextPriorityLevel);
	
		// ...
	
	    if (!next) {
	      // If this doesn't spawn new work, complete the current work.
	      next = completeUnitOfWork(workInProgress);
	    }
	
	    ReactCurrentOwner.current = null;
	    // ...
	    return next;
   }

ReactFiberBeginWork.js

function beginWork(current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime): Fiber | null {
	...
	switch (workInProgress.tag) {
	    case IndeterminateComponent: ...
	    case LazyComponent:...
	    case FunctionComponent: {
	       ...
	      return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderExpirationTime);
	    }
	    ...
	}
}
function updateFunctionComponent(workInProgress, ...) {
  ...
  return workInProgress.child
}

performUnitOfWork动态图

下图fiber结构中a1为root,b1, b2, b3是a1的child。

一个节点(unit work)所有的child完成后该节点即为完成(completeUnitOfWork)
动态performUnitOfWork

commitLifeCycles

ReactFiberCommitWork.js

commit阶段的重要函数,在这里会根据不同的情况调用业务代码中的周期函数

// 根据current是否存在判断调用componentDidMount 还是 componentDidUpdate
function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedExpirationTime: ExpirationTime,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: ...
    
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.effectTag & Update) {
        if (current === null) {
          startPhaseTimer(finishedWork, 'componentDidMount');
          instance.componentDidMount();
          stopPhaseTimer();
        } else {
          const prevProps =
            finishedWork.elementType === finishedWork.type
              ? current.memoizedProps
              : resolveDefaultProps(finishedWork.type, current.memoizedProps);
          const prevState = current.memoizedState;
          startPhaseTimer(finishedWork, 'componentDidUpdate');
          // We could update instance props and state here,
          instance.componentDidUpdate(
            prevProps,
            prevState,
            instance.__reactInternalSnapshotBeforeUpdate,
          );
          stopPhaseTimer();
        }
      }
      const updateQueue = finishedWork.updateQueue;
      if (updateQueue !== null) {
        // 这里会调用state或props改变的callback函数
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    case HostRoot: ...
    case HostComponent: ...
    case HostText: ...
    case HostPortal: ...
    case Profiler:...
    case SuspenseComponent:
	...
    default: ...
  }
}

commitLifeCycles 运行实例:
在这里插入图片描述

Fiber Debugger

Fiber Debugger是React官方的一个图形化展示fiber运行过程的动态demo.

点击Edit按钮,可将以下代码复制到编辑框,点击运行Run即可查看

log('Render <div>Hello</div>');
ReactNoop.render(<div>Hello</div>);
ReactNoop.flush();

log('Render <h1>Goodbye</h1>');
ReactNoop.render(<h1>Goodbye</h1>);
ReactNoop.flush();

React Fiber源码就先看到这里,代码非常浩繁复杂,难免有理解不对的地方,如有大牛偶尔路过,希望不吝赐教:)

没有更多推荐了,返回首页