6

一、什么是React

React是什么?React的官网是这样介绍的:
React-用于构建用户界面的 JavaScript 库。
看起来十分简洁,React仅仅是想提供一个构建用户界面的工具,也就是只关心UI层面,不关心数据层面,数据层面的东西交给专门的人(react-redux)来做。所以有些人会说React就是一个ui框架。
React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。所以,React提供了jsx语法,也可以理解为让你在ul组件里写很多东西。jsx最终是通过babel将组件转化为React.createElement()函数来调用。组件化开发的乐趣需要慢慢体会。既然是入门,下面通过搭建项目-小例子-引入路由/状态管理简单介绍下react,有关复杂的概念这里暂不提及。

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById('root')
);

二、项目搭建

上面说了,react的组件开发是需要babel进行编译的,浏览器是不能直接解析的,所以react项目一般都是需要做一些简单的配置的。当然,有很多的配置完善的脚手架可以使用,比如官方脚手架creat-react-app或者蚂蚁金服的dva框架,使用起来都是很方便上手。但是咱们我觉得通过简单搭建一个项目可以更好的理解react。下面将会通过webpack一步一步搭建一个可以使用的项目,但是具体的优化这里就先不考虑了。

1.打开终端、创建项目

mkdir react-demo && cd react-demo //创建文件夹,并进入文件夹

2.初始化构建

npm init //为了后面下载npm包和node配置使用
         //一路回车键就可以了!项目中多出来一个package.json文件

3.创建项目入口

新建app.js文件,引入react和react-dom,新建一个index.html,包含<div id='app'></div>。

    import React from 'react'; // 终端执行 npm i react 下载react
    import ReactDom from 'react-dom' // 终端执行 npm i react-dom 下载react-dom
    
    function App(){ //以函数的方式创建react组件
        return <div>welcom,react-app</div>
    }
    
    ReactDom.render( //将组件App渲染挂载到页面的根节点app
        <App />,
        document.getElementById('app')//所以需要新建一个html文件提供app节点供组件挂载
    )

3.webpack配置

创建好入口文件app.js后,需要将jsx语法文件通过babel编译,所以先引入webpack,并新建js文件webpack.config.js做相关的配置如下:

npm i webpack webpack-cli --save //安装webpack及cli依赖

webpack.config.js文件内容:

const path = require("path");
const {CleanWebpackPlugin} = require('clean-webpack-plugin');//通过 npm 安装
const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
module.exports = {
    entry:{
        app:'./app.js'
    },
    output: {
        path: path.resolve(__dirname, './dist'),
        filename: '[name].bundel.[hash].js'
    },
    module:{
        rules: [{
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            use:[{
                loader: 'babel-loader',
                options: {
                  presets: [
                      '@babel/preset-env',//引入babel
                      '@babel/preset-react' //引入babel-react 
                    ]
                }
            }]
        }]
    },
    plugins: [
        new CleanWebpackPlugin(),//清空dist文件夹
        new HtmlWebpackPlugin({
            template: './index.html'
        }),//添加html模版插件
    ]
}


安装依赖:
npm install babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env @babel/preset-react --save

npm i html-webpack-plugin clean-webpack-plugin --save

4.配置npm/script启动项

打开package.json文件,添加一行script代码,如下:

clipboard.png

此时我们的代码结构如下:

clipboard.png

当我们在终端执行npm run build命令后,可以看到多出来打包后的dist文件夹,以及打包后的js文件,如下:

clipboard.png

在浏览器里打开dist下的index.html文件,OK,第一行react代码闪亮出现!

clipboard.png

4.修改npm/script启动项
上面代码虽然显示出来了,但是不能每次更改代码都执行一次打包吧,太麻烦了?那就搭建一个server吧,在搭配上局部热更新(hmr)使用起来很是舒畅。需要修改3个地方:

1.在webpack.config.js文件中添加以下代代码:
devServer: {//开启一个本地服务
        port: 9000,//端口
        host:"0.0.0.0",//配置后可通过本机ip访问,方便在手机上调试
        hot:true,//
}
2.修改入口文件app.js:
import React from 'react';
import ReactDOM from 'react-dom'
import {hot} from 'react-hot-loader/root'//添加依赖,通过npm下载

function AppCont(){
    return <div>welcom,react-app hot</div>
}
const App = hot(() =>{//根组件外层通过高阶组件包裹
    return <AppCont />
})

ReactDOM.render(
    <App />,
    document.getElementById('app')
)
3.修改package.json文件:增加一个新的script命令dev

"dev": "webpack-dev-server --mode=development --config  webpack.config.js"

执行npm run dev命令,打开http://localhost:9000/,可以看到页面内容出现了,在文字后面添加hot后,发现页面内容更新了,但是没有刷新!ok到这里,简单的项目已经搭建完成,可以进行开发了。

clipboard.png

三、React的一些概念

这里通过一个简单的例子来描述一下react的一些概念。输入框输入数字、点击后面add+数据会改变并求和。

clipboard.png

import React from 'react';

function Item (props){
    const {count,onChange,onAdd} = props;
    return <div>
        <input value={count} onChange={onChange} />
        <span onClick={onAdd}>add+</span>
    </div>
}

class Add extends React.Component{
    constructor(){
        super();
        this.state = {
            addList : [0,0,0,0]
        }
        this.onChange = this.onChange.bind(this)
        this.onAdd = this.onAdd.bind(this)
    }
    onChange(e,index){
        this.state.addList.splice(index,1,e.target.value)
        this.setState({
            addList:this.state.addList
        })
    }
    onAdd(e){
        this.state.addList.splice(e,1,Number(this.state.addList[e]) + 1 )
        this.setState({
            addList:this.state.addList
        })
    }
    render (){
        const { addList } = this.state;
        return <div>
            {
                addList.map((item,index)=>{
                   return <Item key={index} count={item} onChange={(e)=>{this.onChange(e,index)}} onAdd={()=>{this.onAdd(index)}}  />
                })

            }
            <div>{addList[0]*1 + addList[1]*1 + addList[2]*1 + addList[3]*1}</div>
        </div>
    }
}

export default Add;

麻雀虽小...用来入门也是够用了!

1.react组件的创建的3种方式

1.React.createClass
常用的是class继承和无状态函数的方式,还有一种React.createClass过于繁琐而且会导致不必要的性能开销所以基本上算是被废弃了。
2.无状态组件
直接通过函数声明编写,一般作为纯展示组件使用,内部没有state(可以通过hooks使用),不会实例化,也就不需要分配多余的内存,从而性能得到一定的提升。但是内部无法使用this,也没有生命周期
3.React.Component
React目前极为推宠的创建有状态组件的方式。内部可以使用this和生命周期。可以使用state。
React.Component创建的组件,其成员函数不会自动绑定this,需要手动绑,否则this不能获取当前组件实例对象。绑定方式有3种:可以在构造函数中完成绑定(constructor推荐),也可以在调用时使用method.bind(this)来完成绑定,还可以使用arrow function来绑定。

2.参数传递

一般就是父向子传递参数通过props传递。
子向父传递则要通过父组件传递的函数来传递。
跨组件传递后面会提到使用redux想怎么传就怎么传。

3.受控组件/非受控组件

表单元素除了收到state控制,还会受到用户输入控制。有两个来源,使得内容不可预测。React使用state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
可以看到要给受控组件提供onChange函数来保证state数据的统一性。

三、引入redux

上面看到了父子组件间的传参还很方便的,但是如果要在Add组件外(也就是跨组件传递)使用求和后的数据怎么办?
当然,可以使用增加中间组件、context等方法,这里讲一下大型项目常用的redux状态管理,redux本身的原理也很简单。
1.提供createStore创建store库,将数据都存放在一起,单向数据流。
2.数据只读,修改数据只能通过dispatch方法,传递action对象(必须含有type属性)在纯函数reduce里修改,每一次都是拷贝一个新的数据,不会修改原来的数据源,保证了数据的来源可溯性。

修改app.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux'; //添加redux依赖后,引入创建store的方法createStore
import {Provider} from 'react-redux';//添加react-redux依赖,引入改节组件
import reduce from './model/reduce';//createStore方法第一个参数是reduce
import {hot} from 'react-hot-loader/root';
import AddItem from './component/addItem';


const store = createStore(reduce)

function AppCont(){
    return <Provider store={store}><AddItem /></Provider>//根组件外面包裹高阶组件
}
const App = hot(() =>{
    return <AppCont />
})

ReactDOM.render(
    <App />,
    document.getElementById('app')
)

新建reduce文件,处理reduce函数。由于后面项目庞大后,reduce函都写在一起肯定太臃肿,redux提供了拆分的方法,可以通过业务进行拆分,最后通过combineReducers来合并。

import {combineReducers} from 'redux';
const initState = {
    addList:[0,0,0,0]
}

function addSum(state=initState,action){
    switch (action.type){
        case 'add' : 
            return {
                ...state,
                addList:[...action.payload]
            }
        default : return {...state}
    }
    
}
export default combineReducers({
    addSum
})

addItem文件需要做一些修改,将原来写在state的数据,通过store来存取。修改方法跟下面新增加Sum组件一样:通过connect函数来连接组件和store。

import React from 'react';
import {connect} from 'react-redux';
function Sum(props){
    const {addList} = props
    return <div>{
        Number(addList[0]) + Number(addList[1]) + Number(addList[2]) + Number(addList[3])
    
    }</div>
}
export default connect(data=>data.addSum)(Sum);

四、引入react-router

react单页面模式下,路由显得很重要了,react将router单独剔除,形成一个单独的组件,使用起来更加的方便,代码的耦合程度也降低不少。
提供HashRouter、BrowserRouter两种根路由方式,重要的区别是HashRouter是通过hash路径访问,在浏览器直接输入地址可以访问,BrowserRouter通过h5的history api访问,输入地址时会像服务器查询,因为是单页面,没有历史所以查询不到,不过通过我们使用webpack的devserver配置historyApiFallback: true,可以访问,服务端渲染也可以访问。

    npm i react-router-dom --save
 <Router>
    <Route path="/" component={AddItem}></Route>
    <Link to="/tab1/1">tab1</Link>
    <Link to="/tab2/2">tab2</Link>
    <Link to="/tab3/3">tab3</Link>
    <Route path="/tab1/:num" component={PageItem}></Route>
    <Route path="/tab2/:num" component={PageItem}></Route>
    <Route path="/tab3/:num" component={PageItem}></Route>
 </Router>

OK,到这里一个简单的react开发框架搭建完毕,看到这里应该入门了吧。

五、引入副作用处理saga

我们说reduce要写成纯函数,写成没有副作用的函数,那么有副作用的事件怎么办?最常见的就是接口的调用。不要慌!redux提供了很多中间件供我们处理副作用函数,也可以自己写,原理就是在dispath(action)前面进行副作用的操作,拿到数据后再进行dispatch。下面引入一个比较好用的处理接口的中间件saga:

给store添加第二个参数:
import { watchIncrementAsync } from './saga/index'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(addSum, applyMiddleware(sagaMiddleware));

saga/index.js
import { put, takeEvery,delay } from 'redux-saga/effects'

export function* incrementAsync() {
  console.log(delay)
   yield delay(2000)
   yield put({ type: 'add',payload:[0,0,0,0] })
}

export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

需要注意的是,引入redux-saga依赖,因为saga是基于generator语法编写的异步处理方案,所以需要对babel进行配置,npm i @babel/plugin-transform-runtime --save引入runtime依赖。并做以下配置修改:

options:{
    "plugins": ["@babel/plugin-transform-runtime"]
}

对store等文件做了一些修改,完整代码,放到git上了代码地址

六、提交git

之前都是先在git端新建项目,然后clone到本地在开发,这次是直接在本地创建的项目,然后在提交的,中间除了一点小问题,就当在这记录一下吧。
1.githab新建仓库,获取地址。

clipboard.png

clipboard.png

2.打开终端,进入项目目录下,git init //初始化

3.git add * //添加所有更改

4.git commit -m "提交备注"

5.git remote add origin https://github.com/wyczmy/rea...
//链接远程仓库

6.git pull --rebase origin master //远程如果创建时有README.md文件,需要执行

7.git push -u origin master //推送到远程 -f 强制推送


如有不妥,欢迎指正!


搁浅
693 声望323 粉丝

想进BAT的前端er