zoukankan      html  css  js  c++  java
  • React+Node初尝试

    这是第一次写React和Node,选用的是前端Material-ui框架,后端使用的是Express框架,数据库采用的是Mongodb。

    项目代码在:GitHub/lilu_movie , 欢迎大家关注或提问题。

    这是一个通过从电影天堂抓取数据并显示的电影网站,demo部署在heroku上面。

    安装:

    首先安装express框架;

    npm install express --save

    生成文件后,可以通过npm start启动应用。

    注意:ejs 从3.x后不支持layout,可以通过express-partials ,但是不支持4,4之后用include

    紧接着我迫不及待安装material-ui:

    npm install material-ui --save

    然后出现错误:

    所以必须安装react依赖:

    npm install react@^15.0.0 --save
    
    npm install react-dom@^15.0.0 --save
    
    npm install react-tap-event-plugin@^1.0.0 --save 

    安装nodemon

    nodemon ./bin/www #更改会自动重启服务

    本地安装数据库mongodb

    然后npm安装操作mongodb的mongoose 

    npm install mongoose
    
    npm install express-mongoose 

    接着你会发现按照material-ui的import引入报错,

    使用es6查看系统支持哪些es6语法

    npm install es6-checker

    因为react使用es6和jsx语法,所以需要转化,安装如下包:

    npm install babel-loader babel-core babel-preset-es2015 —save-dev
    
    npm install jsx-loader —save-dev
    
    npm install babel-preset-react

    安装webpack

    npm install webpack —save-dev
    
    
    npm install css-loader —save-dev
    
    
    npm install webpack-dev-server —save-dev

    这里有必要提一下:-save-dev代表安装的包适用于开发的,类似于rails中安装Gem放在:development环境下,这样生产环境就不会安装。

    然后在webpack配置文件中babels的loaders中query加入presets

    因为需要一些css文件,react通过require style文件,需要安装

    npm install style-loader —save-dev
    
    npm install css-loader —save-dev // 这个和style一起用才有效果

    在webpack中的config 加上loader: "style-loader!css-loader”,就不用require使用style!css!了

    启动脚本: 

    配置package.json文件,给script添加命令

      "start": ["node ./bin/www", "webpack”],

    编写webpack.config.js配置文件,更改html引入文件

    在webpack-dev-server 没有真正生成文件,还得要引入<script src=“localhost:8080/assets/bundle.js"></script>

    运行npm run dev,看webpack-dev-server效果

    Express后端流程改变: 

    刚开始,我用一贯的后台思路通过routes渲染页面,页面html引入react的js文件,reactjs文件link后台js响应;后台相应通过连接mongodb获取数据库内容。

    很成功,获取到相应的内容了,但是因为使用react(React适合做SPA),所以不好每次都取加载一次内容,所以就不用引擎模板,这些数据如何放入state让react用diff算法自己计算呢?怎样变成单页面应用呢?

    这时候我想到就是ajax;上网google一下,发现用fetch能实现像ajax那样的请求。同一个component可以很容易实现fetch数据改变this的state。

    详情更改请看我的这个commit:add client store to fetch movie's data from server

    这时候发现不知道怎么通过点击侧边栏标签,渲染新页面(注意:侧边栏是一个组件,而右边的电影列表又是一个组件,他们是两个不同的组件

    使用React-Router

    由于react规定父元素只能改变子元素,但是不好将子元素改变父元素;

    一般我们都会把许多内容都搞到最顶级那个父元素的state,这样其他都有可能与他有关联,而子元素改变父元素的state的方法就是通过回调setState;

    所以这里我们可以将movies放在最顶的component,然后点击标签,就去回调去改变这个父元素的state,用到这个state的子元素就会刷新。

     代码详见这个commit

    但是这样太hacky了,违背react理念,代码难理解

    有没有其他更好的办法呢?

    Google查找答案发现有两种方法:

    1.  使用react-router

    2.  如果不是用react-router,则得这样写https://github.com/ReactTraining/react-router/blob/master/docs/Introduction.md

    react-route根据history传入的链接,找到你对应routes的component,然后改变children,成功渲染改组件。

    对于不同组件改变同个内容还是使用react-router

    使用react-router发现client端通过router的链接,局部更新内容;

    这样子说,完全不需要server后台每个路由每次渲染不同页面了,只需要server不同链接给出不同内容,然后渲染同一个页面,这个页面通过react-router去改变内容即可。

    所以删除后端所有router路由;

    按照React Router官方教程实现相应代码。

    这时候发现一个问题:

    渲染同一个页面就要在后端引入前端的routes,也就需要到es6了,但是之前后端没有通过webpack进行es6的转化,所以还要对后端的入口文件进行webpack转化。

    对后端server.js进行webpack的bundle后,很容易报错,首先要在web pack中排除掉node_module的文件,然后需要引入各种loader; 

    server端要import client端的routes过来,但是route的component会引用相应的component。如果遇到client的内容,有些react-router/server是处理不了的,会报没有window错误。

     以下是最终的webpack.server.config.js 

    var webpack = require("webpack");
    var fs = require("fs");
    var path = require("path");
    
    module.exports = {
      entry: [
        path.resolve(__dirname, 'server.js')
      ],
    
      output: {
        filename: 'server.bundle.js'
      },
      target: 'node',
    
      externals: fs.readdirSync(path.resolve(__dirname, 'node_modules')).concat([
        'react-dom/server', 'react/addons',
      ]).reduce(function (ext, mod) {
        ext[mod] = 'commonjs ' + mod
        return ext
      }, {}),
    
      node: {
        __filename: true,
        __dirname: true
      },
      module: {
        loaders: [
          {
            test: /.css$/,
            loader: "style-loader!css-loader"
          }, {
            test: /.js$/,
            exclude: /node_modules/,
            loader: "babel",
            query: { presets: ['react', 'es2015'] }
          }, {
            test: /.json$/, loader: 'json-loader'
          }, {
            test: /.node$/, loader: 'node-loader'
          }]
    
      }
    }

    事实上使用react react-router已经足够完成功能,但是考虑到如果之后想要扩展,例如现在我又要增加用户注册,登录页面;那么很有可能随着页面组件增多,这个项目将会变成意大利面;

    使用Redux

    这时候应该引入redux;

    所以我又npm install react-redux, redux;

    关于redux,其实应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。惟一改变 state 的办法是触发 action,一个描述发生什么的对象。

    为了描述 action 如何改变 state 树,你需要编写 reducers

    通过写reducer,可以很好的管理actions,这样更加条理。可以看我的redux目录

    • actions:用来fetch后台数据或者直接返回改变的内容
    • reducers:通过actions返回的数据,改变state
    • constants:每个actions的常量名
    • configureStore.js:通过传入reducers和初始的states,返回store(包含改变的states)

    然后前台app.js渲染之前,必须先通过<Provide>传入store;

    const store = configureStore(window.__INITIAL_STATE__)
    
    /* 这里store存储某一时刻的state树,所以这里面通过action得到的states是不一样的
     * 例如:如果一开始给tags,后面action没有给tags,则不会有原来的初始值tags,所以不用担心数据冗余。
     */
    
    ReactDOM.render(
      <Provider store={store} >
        <Router routes={routes} history={browserHistory} />
      </Provider>,
      document.getElementById('app')

    后台呢?也要相应根据么次fetch数据返回新的store。

          const store = configureStore(initialState)
          const state = store.getState()
          const params = Object.assign(req.query, renderProps.params)
    
          fetchComponentDataBeforeRender(store.dispatch, renderProps.components, params)
          .then(() => {
            // 这里redux的store给Provider,再通过mapStateToProps给对应的容器组件
            const html = renderToString(
              <Provider store={store}>
                <RouterContext {...renderProps} />
              </Provider>
            )

    有了后台传回来新的store,如何应用到前台组件呢?

    就在组件中引入connect:

    import { connect } from 'react-redux'
    class MoviesGridList extends React.Component {
    ... ....
    // 将store最新的state.movies给props,
    function mapStateToProps(state, props) {
      let tags = props.location.query.tags
      let movies
      if (tags === undefined) {
        movies = state.movies
      } else {
        movies = getMoviesByTag(state, tags)
      }
      return {
        tags: tags,
        movies: movies
      }
    }
    
    export default connect(mapStateToProps)(MoviesGridList)

    本文内容有待更新,具体代码和问题详见Github仓库的commit

  • 相关阅读:
    [转]relative、absolute和float
    CSS 布局属性(display,float,clear,visibility,overflow,overflow-x,overflow-y)
    CSS 块状元素和内联元素
    CSS定位
    CSS实例
    jQuery简单实例
    [转]几种常见SQL分页方式
    MyBatis Mapper XML 文件
    MyBatis XML 映射配置文件
    MyBatis 入门
  • 原文地址:https://www.cnblogs.com/Caikejia/p/6639182.html
Copyright © 2011-2022 走看看