UDN-企业互联网技术人气社区

板块导航

浏览  : 380
回复  : 3

[React] React全家桶实现一个简易备忘录

[复制链接]
小辫儿的头像 楼主
  总括:本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应用的过程。

  代码地址: Github代码地址

  技术架构:本备忘录使用react+react-router+redux+less+ES6+webpack实现

  页面UI参照: TodoList官网 实现;

  在线演示地址: Damonare的备忘录 ;

  功能说明

  • 支持回车添加新事项;
  • 支持删除事项(点击X符号);
  • 支持状态转换具体包括:
  • 新建事项->正在进行(点击checkbox选项)
  • 正在进行->已完成(点击文字内容本身)
  • 正在进行->新建事项(点击checkbox选项)
  • 已完成->正在进行(点击文字本身)
  • 支持判断输入空字符,过长字符(20个汉字以内);
  • 支持搜索;
  • 支持本地化存储;
  • 支持状态的展开隐藏(点击标题)
  • 兼容手机端(iPhone6及以上)
  • 支持路由切换

  正文

  1. React浅谈

  1.1 组件化

  ​ 毫无疑问,当谈到 React 的时候不能避免的会提到组件化思想。React刚开始想解决的问题只是UI这一层面的问题,也就是MVC中view层面的问题,不成想如今越滚越大,从最早的UI引擎变成了一整套前后端通吃的 Web App 解决方案。对于 React 组件的理解同样要站在view层面的角度出发,一个完整的页面是由大大小小的组件堆叠而成,就好像搭积木,每一块积木都是一个组件,组件套组件组成了用户所能看到的完整的页面。

  1.2 JSX语法糖

  ​ 使用 React ,不一定非要使用 JSX 语法,可以使用原生的JS进行开发。但是 React 作者强烈建议我们使用 JSX ,因为 JSX 在定义类似HTML这种树形结构时,十分的简单明了。这里简单的讲下 JSX 的由来。

  ​ 比如,下面一个div元素,我们用HTML语法描述为:
  1. <divclass="test">
  2.   <span>Test</span>
  3. </div>
复制代码

  如果换做使用javascript描述这个元素呢?最好的方式可以简单的转化为 json 对象,如下:
  1. {
  2.   type:"div",
  3.   props:{
  4.     className:"test",
  5.     children:{
  6.       type:"span",
  7.       props:{
  8.         children:"Test"
  9.       }
  10.     }
  11.   }
  12. }
复制代码

  这样我们就可以在javascript中创建一个 Virtual DOM (虚拟DOM)了。当然,这样是没法复用的,我们再把它封装一下:
  1. const Div=>({text}){
  2.   return {
  3.     type:"div",
  4.     props:{
  5.       className:"test",
  6.       children:{
  7.         type:"span",
  8.         props:{
  9.           children: text,
  10.         },
  11.       },
  12.     },
  13.   }
  14. }
复制代码

  接下来再实现这个div就可以直接调用Div(‘Test’)来创建。但上述结构看起来实在让人不爽,写起来也很容易写混,一旦结构复杂了,很容易让人找不着北,于是 JSX 语法应运而生。我们用写HTML的方式写这段代码,再经过翻译器转换成javascript后交给浏览器执行。上述代码用 JSX 重写:
  1. const Div =()=>(
  2. <divclassName="test">
  3.   <span>Test</span>
  4. </div>
  5. );
复制代码

  多么简单明了!!!具体的 JSX语法 不多说了,学习更多戳这: JSX in Depth

  1.3 Virtual DOM

  其实上面已经提到了 Virtual DOM ,它的存在也是 React 长久不衰的原因之一,虚拟DOM的概念并不是FB首创却在FB的手上大火了起来(后台是多么重要)。

  我们知道真实的页面对应了一个DOM树,在传统页面的开发模式中,每次需要更新页面时,都需要对DOM进行更新,DOM操作十分昂贵,为减少对于真实DOM的操作,诞生了 Virtual DOM 的概念,也就是用javascript把真实的DOM树描述了一遍,使用的也就是我们刚刚说过的 JSX 语法。对比如下:
1.png
  每次数据更新之后,重新计算 Virtual DOM ,并和上一次的 Virtual DOM 对比,对发生的变化进行批量更新。React也提供了 shouldComponentUpdate 生命周期回调,来减少数据变化后不必要的 Virtual DOM 对比过程,提升了性能。

  Virtual DOM 虽然渲染方式比传统的DOM操作要好一些,但并不明显,因为对比DOM节点也是需要计算的,最大的好处在于可以很方便的和其它平台集成,比如 react-native 就是基于 Virtual DOM 渲染出原生控件。具体渲染出的是 Web DOM 还是 Android 控件或是 iOS 控件就由平台决定了。所以我们说 react 的出现是一场革命,一次对于 native app 的宣战,就像 react-native 那句口号—— Learn Once,Write Anywhere .

  1.4 函数式编程

  ​ 过去编程方式主要是以命令式编程为主,什么意思呢?简单说电脑的思维方式和我们人类的思考方式是不一样的。我们人类的大脑擅长的是分析问题,提出一个解决问题的方案,电脑则是生硬的执行指令,命令式编程就像是给电脑下达命令,让电脑去执行一样,现在主要的编程语言(比如:Java,C,C++等)都是由命令式编程构建起来的。

  ​ 而函数式编程就不一样了,这是模仿我们人类的思维方式发明出来的。例如:操作某个数组的每一个元素然后返回一个新数组,如果是计算机的思考方式,会这样想:创建一个新数组=>遍历旧数组=>给新数组赋值。如果是人类的思考方式,会这样想:创建一个数组方法,作用在旧数组上,返回新数组。这样此方法可以被重复利用。而这就是函数式编程了。

  1.5 数据流

  在React中,数据的流动是单向的,即从父节点传递到子节点。也因此组件是简单的,他们只需要从父组件获取props渲染即可。如果顶层的props改变了,React会递归的向下遍历整个组件树,重新渲染所有使用这个属性的组件。那么父组件如何获取子组件数据呢?很简单,通过回调就可以了,父组件定义某个方法供给子组件调用,子组件调用方法传递给父组件数据,Over。

  2. React-router

  这东西我觉得没啥难度,官方例子都很不错,跟着官方例子来一遍基本就明白到底是个啥玩意了,官方例子:react-router-tutorial

  还有啥不明白的欢迎评论留言共同探讨。

  3. Redux

  3.1 简介

  随着 JavaScript 单页应用开发日趋复杂, JavaScript 需要管理比任何时候都要多的 state (状态) 。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。乱!

  这时候 Redux 就强势登场了,现在你可以把 React 的model看作是一个个的子民,每一个子民都有自己的一个状态,纷纷扰扰,各自维护着自己状态,我行我素,那哪行啊!太乱了,我们需要一个King来领导大家,我们就可以把 Redux 看作是这个King。网罗所有的组件组成一个国家,掌控着一切子民的状态!防止有人叛乱生事!

  这个时候就把组件分成了两种:容器组件(King或是路由)和展示组件(子民)。

  容器组件:即 redux 或是 router ,起到了维护状态,出发action的作用,其实就是King高高在上下达指令。

  展示组件:不维护状态,所有的状态由容器组件通过 props 传给他,所有操作通过回调完成。

展示组件
容器组件
作用
描述如何展现(骨架、样式)
描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源
props
监听 Redux state
数据修改
从 props 调用回调函数
向 Redux 派发 actions
调用方式
手动
通常由 React Redux 生成

  Redux三大部分: store , action , reducer 。相当于King的直系下属。

  那么也可以看出 Redux 只是一个状态管理方案,完全可以单独拿出来使用,这个King不仅仅可以是React的,去Angular,Ember那里也是可以做King的。在React中维系King和组件关系的库叫做 react-redux 。

  它主要有提供两个东西: Provider 和 connect

  3.2 Store

  Store 就是保存数据的地方,它实际上是一个 Object tree 。整个应用只能有一个 Store。这个Store可以看做是King的首相,掌控一切子民(组件)的活动(state)。

  Redux 提供 createStore 这个函数,用来生成 Store。
  1. import { createStore } from 'redux';
  2. const store = createStore(func);
复制代码

  createStore接受一个函数作为参数,返回一个Store对象(首相诞生记)

  我们来看一下Store(首相)的职责:

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。

  3.3 action

  State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。即store的数据变化来自于用户操作。action就是一个通知,它可以看作是首相下面的邮递员,通知子民(组件)改变状态。它是 store 数据的 唯一 来源。一般来说会通过 store.dispatch() 将 action 传到 store。

  Action 是一个对象。其中的 type 属性是必须的,表示 Action 的名称。
  1. const action = {
  2.   type: 'ADD_TODO',
  3.   payload: 'Learn Redux'
  4. };
复制代码

  Action创建函数

  Action 创建函数就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。

  在 Redux 中的 action 创建函数只是简单的返回一个 action:
  1. functionaddTodo(text){
  2.   return {
  3.     type: ADD_TODO,
  4.     text
  5.   }
  6. }
复制代码

  这样做将使 action 创建函数更容易被移植和测试。

  3.4 reducer

  Action只是描述了 有事情发生了 这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。也就是邮递员(action)只负责通知,具体你(组件)如何去做,他不负责,这事情只能是你们村长(reducer)告诉你如何去做才能符合社会主义核心价值观,如何做才能对建设共产主义社会有利。

  专业解释: Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

  Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
  1. const reducer = function(state, action){
  2.   // ...
  3.   return new_state;
  4. };
复制代码

  3.5 数据流

  严格的单向数据流是 Redux 架构的设计核心。

  Redux 应用中数据的生命周期遵循下面 4 个步骤:

  • 调用 store.dispatch(action) 。
  • Redux store 调用传入的 reducer 函数。
  • 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
  • Redux store 保存了根 reducer 返回的完整 state 树 。

  工作流程图如下:
1.png
  3.6 Connect

  这里需要再强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。

  尽管如此,Redux 还是和React 和 Deku 这类框架搭配起来用最好,因为这类框架允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。

  Redux 默认并不包含 React 绑定库 ,需要单独安装。
  1. npm install --save react-redux
复制代码

  当然,我们这个实例里是不需要的,所有需要的依赖已经在package.json里配置好了。

  React-Redux 提供 connect 方法,用于从 UI 组件生成容器组件。 connect 的意思,就是将这两种组件连起来。
  1. import { connect } from 'react-redux';
  2. const TodoList = connect()(Memos);
复制代码

  上面代码中 Memos 是个UI组件, TodoList 就是由 React-Redux 通过 connect 方法自动生成的容器组件。

  而只是纯粹的这样把Memos包裹起来毫无意义,完整的connect方法这样使用:
  1. import { connect } from 'react-redux'
  2. const TodoList = connect(
  3.   mapStateToProps
  4. )(Memos)
复制代码

  上面代码中, connect 方法接受两个参数: mapStateToProps 和 mapDispatchToProps 。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将 state 映射到 UI 组件的参数( props ),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。

  3.7 Provider

  这个Provider 其实是一个中间件,它是为了解决让容器组件拿到King的指令( state 对象)而存在的。
  1. import { Provider } from 'react-redux'
  2. import { createStore } from 'redux'
  3. import todoApp from './reducers'
  4. import App from './components/App'
  5. let store = createStore(todoApp);
  6. render(
  7.   <Providerstore={store}>
  8.     <App/>
  9.   </Provider>,
  10.   document.getElementById('root')
  11. )
复制代码

  上面代码中, Provider 在根组件外面包了一层,这样一来, App 的所有子组件就默认都可以拿到 state 了。

  4.实战备忘录

  4.1目录结构
  1. .
  2. ├── app                 #开发目录
  3. ||
  4. |├──actions #action的文件
  5. ||
  6. |├──components #展示组件
  7. ||
  8. |├──containers #容器组件,主页
  9. ||
  10. |├──reducers #reducer文件
  11. ||
  12. ||——routes #路由文件,容器组件
  13. ||
  14. ||——static #静态文件
  15. ||
  16. |├──stores #store配置文件
  17. ||
  18. ||——main.less #路由样式
  19. ||
  20. |└──main.js #入口文件
  21. |
  22. ├── build                #发布目录
  23. ├── node_modules        #包文件夹
  24. ├── .gitignore     
  25. ├── .jshintrc      
  26. ├── webpack.production.config.js  #生产环境配置      
  27. ├── webpack.config.js   #webpack配置文件
  28. ├── package.json        #环境配置
  29. └── README.md                   #使用说明
复制代码

  接下来,我们只关注app目录就好了。

  4.2入口文件
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import {Route, IndexRoute, browserHistory, Router} from 'react-router';
  4. import {createStore} from 'redux';
  5. import {Provider} from 'react-redux';
  6. import App from './container/App';
  7. import AllMemosRoute from './routes/AllMemosRoute';
  8. import TodoRoute from './routes/TodoRoute';
  9. import DoingRoute from './routes/DoingRoute';
  10. import DoneRoute from './routes/DoneRoute';
  11. import configureStore from './stores';
  12. import './main.less';
  13. const store = configureStore();
  14. ReactDOM.render(
  15.     <Provider store={store}>
  16.         <Router history={browserHistory}>
  17.             <Route path="/"  component={App}>
  18.                 <IndexRoute component={AllMemosRoute}/>
  19.                 <Route path="/todo" component={TodoRoute}/>
  20.                 <Route path="/doing" component={DoingRoute}/>
  21.                 <Route path="/done" component={DoneRoute}/>
  22.             </Route>
  23.         </Router>
  24.    </Provider>,
  25. document.body.appendChild(document.createElement('div')))
复制代码

  这里我们从 react-redux 中获取到 Provider 组件,我们把它渲染到应用的最外层。

  他需要一个属性 store ,他把这个 store 放在context里,给Router(connect)用。

  4.3 Store

  app/store/index.jsx
  1. import { createStore } from 'redux';
  2. import reducer from '../reducers';
  3. export default functionconfigureStore(initialState){
  4.   const store = createStore(reducer, initialState);
  5.   if (module.hot) {
  6.     // Enable Webpack hot module replacement for reducers
  7.     module.hot.accept('../reducers', () => {
  8.       const nextReducer = require('../reducers');
  9.       store.replaceReducer(nextReducer);
  10.     });
  11.   }
  12.   return store;
  13. } 
复制代码

  4.4 Action 创建函数和常量

  app/action/index.jsx
  1. 'use strict';
  2. /*
  3. * @author Damonare 2016-12-10
  4. * @version 1.0.0
  5. * action 类型
  6. */
  7. export const Add_Todo = 'Add_Todo';
  8. export const Change_Todo_To_Doing = 'Change_Todo_To_Doing';
  9. export const Change_Doing_To_Done = 'Change_Doing_To_Done';
  10. export const Change_Done_To_Doing = 'Change_Done_To_Doing';
  11. export const Change_Doing_To_Todo = 'Change_Doing_To_Todo';
  12. export const Search='Search';
  13. export const Delete_Todo='Delete_Todo';
  14. /*
  15. * action 创建函数
  16. * [url=home.php?mod=space&uid=45341]@method[/url]  addTodo添加新事项
  17. * @param  {String} text 添加事项的内容
  18. */
  19. export functionaddTodo(text){
  20.   return {
  21.       type: Add_Todo,
  22.       text
  23.   }
  24. }
  25. /*
  26. * @method  search 查找事项
  27. * @param  {String} text 查找事项的内容
  28. */
  29. export functionsearch(text){
  30.   return {
  31.       type: Search,
  32.       text
  33.   }
  34. }
  35. /*
  36. * @method  changeTodoToDoing 状态由todo转为doing
  37. * @param  {Number} index 需要改变状态的事项的下标
  38. */
  39. export functionchangeTodoToDoing(index){
  40.   return {
  41.       type: Change_Todo_To_Doing,
  42.       index
  43.   }
  44. }
  45. /*
  46. * @method  changeDoneToDoing 状态由done转为doing
  47. * @param  {Number} index 需要改变状态的事项的下标
  48. */
  49. export functionchangeDoneToDoing(index){
  50.   return {
  51.       type: Change_Done_To_Doing,
  52.       index
  53.   }
  54. }
  55. /*
  56. * @method  changeDoingToTodo 状态由doing转为todo
  57. * @param  {Number} index 需要改变状态的事项的下标
  58. */
  59. export functionchangeDoingToTodo(index){
  60.   return {
  61.       type: Change_Doing_To_Todo,
  62.       index
  63.   }
  64. }
  65. /*
  66. * @method  changeDoingToDone 状态由doing转为done
  67. * @param  {Number} index 需要改变状态的事项的下标
  68. */
  69. export functionchangeDoingToDone(index){
  70.   return {
  71.       type: Change_Doing_To_Done,
  72.       index
  73.   }
  74. }
  75. /*
  76. * @method  deleteTodo 删除事项
  77. * @param  {Number} index 需要删除的事项的下标
  78. */
  79. export functiondeleteTodo(index){
  80.   return {
  81.       type: Delete_Todo,
  82.       index
  83.   }
  84. }
复制代码

  在声明每一个返回 action 函数的时候,我们需要在头部声明这个 action 的 type,以便好组织管理。

  每个函数都会返回一个 action 对象,所以在 容器组件里面调用
  1. text =>
  2.   dispatch(addTodo(text))
复制代码

  就是调用 dispatch(action) 。

  4.5 Reducers

  app/reducers/index.jsx
  1. import { combineReducers } from 'redux';
  2. import todolist from './todos';
  3. // import visibilityFilter from './visibilityFilter';

  4. const reducer = combineReducers({
  5.   todolist
  6. });

  7. export default reducer;
  8. app/reducers/todos.jsx

  9. import {
  10.     Add_Todo,
  11.     Delete_Todo,
  12.     Change_Todo_To_Doing,
  13.     Change_Doing_To_Done,
  14.     Change_Doing_To_Todo,
  15.     Change_Done_To_Doing,
  16.     Search
  17. } from '../actions';
  18. let todos;
  19. (function(){
  20.     if (localStorage.todos) {
  21.         todos = JSON.parse(localStorage.todos)
  22.     } else {
  23.         todos = []
  24.     }
  25. })();
  26. functiontodolist(state = todos, action){
  27.     switch (action.type) {
  28.             /*
  29.         *  添加新的事项
  30.         *  并进行本地化存储
  31.         *  使用ES6展开运算符链接新事项和旧事项
  32.         *  JSON.stringify进行对象深拷贝
  33.         */
  34.         case Add_Todo:
  35.             localStorage.setItem('todos', JSON.stringify([
  36.                 ...state, {
  37.                     todo: action.text,
  38.                     istodo: true,
  39.                     doing: false,
  40.                     done: false
  41.                 }
  42.             ]));
  43.             return [
  44.                 ...state, {
  45.                     todo: action.text,
  46.                     istodo: true,
  47.                     doing: false,
  48.                     done: false
  49.                 }
  50.             ];
  51.             /*
  52.             *  将todo转为doing状态,注意action.index的类型转换
  53.             */
  54.         case Change_Todo_To_Doing:
  55.             localStorage.setItem('todos', JSON.stringify([
  56.                 ...state.slice(0, action.index),
  57.                 {
  58.                     todo:state[action.index].todo,
  59.                     istodo: false,
  60.                     doing: true,
  61.                     done: false
  62.                 },
  63.                 ...state.slice(parseInt(action.index) + 1)
  64.             ]));
  65.             return [
  66.                 ...state.slice(0, action.index),
  67.                 {
  68.                     todo:state[action.index].todo,
  69.                     istodo: false,
  70.                     doing: true,
  71.                     done: false
  72.                 },
  73.                 ...state.slice(parseInt(action.index) + 1)
  74.             ];
  75.             /*
  76.             *  将doing转为done状态
  77.             */
  78.         case Change_Doing_To_Done:
  79.             localStorage.setItem('todos', JSON.stringify([
  80.                 ...state.slice(0, action.index),
  81.                 {
  82.                     todo:state[action.index].todo,
  83.                     istodo: false,
  84.                     doing: false,
  85.                     done: true
  86.                 },
  87.                 ...state.slice(parseInt(action.index) + 1)
  88.             ]));
  89.             return [
  90.                 ...state.slice(0, action.index),
  91.                 {
  92.                     todo:state[action.index].todo,
  93.                     istodo: false,
  94.                     doing: false,
  95.                     done: true
  96.                 },
  97.                 ...state.slice(parseInt(action.index) + 1)
  98.             ];
  99.             /*
  100.             *  将done转为doing状态
  101.             */
  102.         case Change_Done_To_Doing:
  103.             localStorage.setItem('todos', JSON.stringify([
  104.                 ...state.slice(0, action.index),
  105.                 {
  106.                     todo:state[action.index].todo,
  107.                     istodo: false,
  108.                     doing: true,
  109.                     done: false
  110.                 },
  111.                 ...state.slice(parseInt(action.index) + 1)
  112.             ]));
  113.             return [
  114.                 ...state.slice(0, action.index),
  115.                 {
  116.                     todo:state[action.index].todo,
  117.                     istodo: false,
  118.                     doing: true,
  119.                     done: false
  120.                 },
  121.                 ...state.slice(parseInt(action.index) + 1)
  122.             ];
  123.             /*
  124.             *  将doing转为todo状态
  125.             */
  126.         case Change_Doing_To_Todo:
  127.             localStorage.setItem('todos', JSON.stringify([
  128.                 ...state.slice(0, action.index),
  129.                 {
  130.                     todo:state[action.index].todo,
  131.                     istodo: true,
  132.                     doing: false,
  133.                     done: false
  134.                 },
  135.                 ...state.slice(parseInt(action.index) + 1)
  136.             ]));
  137.             return [
  138.                 ...state.slice(0, action.index),
  139.                 {
  140.                     todo:state[action.index].todo,
  141.                     istodo: true,
  142.                     doing: false,
  143.                     done: false
  144.                 },
  145.                 ...state.slice(parseInt(action.index) + 1)
  146.             ];
  147.             /*
  148.             *  删除某个事项
  149.             */
  150.         case Delete_Todo:
  151.             localStorage.setItem('todos', JSON.stringify([
  152.                 ...state.slice(0, action.index),
  153.                 ...state.slice(parseInt(action.index) + 1)
  154.             ]));
  155.             return [
  156.                 ...state.slice(0, action.index),
  157.                 ...state.slice(parseInt(action.index) + 1)
  158.             ];
  159.             /*
  160.             *  搜索
  161.             */
  162.         case Search:
  163.         let text=action.text;
  164.         let reg=eval("/"+text+"/gi");
  165.             return state.filter(item=> item.todo.match(reg));
  166.         default:
  167.             return state;
  168.     }
  169. }
  170. export default todolist;
复制代码

  具体的展示组件这里就不罗列代码了,感兴趣的可以戳这: 备忘录展示组件地址

  后记 

  严格来说,这个备忘录并不是使用的react全家桶,毕竟还有一部分less代码,不过这一个应用也算是比较全面的使用了react+react-router+redux,作为react全家桶技术学习的练手的小项目再适合不过了。

相关帖子

发表于 2017-1-5 10:27:10 | 显示全部楼层
回帖支持下楼主,请眼熟我,我叫“小丫精灵“
使用道具 举报

回复

发表于 2017-1-5 10:27:17 | 显示全部楼层
个人觉得js是一种解释性语言,它提供了一个非常方便的开发过程,不需要编译,js与HTML标识结合在一起,从而方便用户的使用操作。
使用道具 举报

回复

发表于 2017-1-5 10:27:18 | 显示全部楼层
不错的帖子,支持下
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部