@tiki.vn/redux-miniprogram-bindings

Redux binding utils for miniprogram

Usage no npm install needed!

<script type="module">
  import tikiVnReduxMiniprogramBindings from 'https://cdn.skypack.dev/@tiki.vn/redux-miniprogram-bindings';
</script>

README

redux-miniprogram-bindings

Redux cho các mini-programs (Tiki, Alipay và Wechat)

Đặc điểm

  • API đơn giản và linh hoạt. Nó có thể được sử dụng dễ dàng chỉ với một connect (cung cấp phương thức tắt - shortcut method - tương tác $page alias và $component alias).
  • Đầy đủ các hàm, phương thức đa dạng đáp ứng các nhu cầu và ngữ cảnh sử dụng khác nhau.
  • Hỗ trợ sử dụng trong TXML.
  • Sau khi dispatch được thực thi, trạng thái nội tại (internal state) của các component tự động được cập nhật; và view được đưa vào hàng đợi để kết xuất cập nhật.
  • Hỗ trợ mini-program của Tiki, WeChat và Alipay.

Cài đặt

  • Cài đặt thông qua npm hay yarn.

    # npm
    $ npm install --save redux-miniprogram-bindings redux
    # yarn
    $ yarn add redux-miniprogram-bindings redux
    
  • Ngoài ra bạn có thể tìm thấy các tập tin phân phối bên trong của thư mục dist của redux-miniprogram-bindings.

  • Tập tin redux cần được import riêng biệt (nó là thư viện binding và không chứa redux code).

Sử dụng

  1. Tạo ra thực thể Redux Store

    // store.js
    import { createStore, combineReducers } from 'redux'
    
    function counter(state = 0, action) {
      switch (action.type) {
        case 'INCREMENT':
          return state + (action.step || 1)
        case 'DECREMENT':
          return state - (action.step || 1)
        default:
          return state
      }
    }
    
    const initUserInfo = { name: 'userName', age: 25 }
    function userInfo(state = initUserInfo, action) {
      switch (action.type) {
        case 'SET_USER_INFO':
          return { ...state, ...action.userInfo }
        default:
          return state
      }
    }
    
    const rootReducer = combineReducers({ counter, userInfo })
    const store = createStore(rootReducer)
    
    export default store
    
  2. Thiết lập provider trong tập tin app.js

    // setupStore.js
    import store from 'your/store/path'
    // micro channel applet
    import { setProvider } from 'redux-miniprogram-bindings'
    //  Alipay applet 
    // import { setProvider } from 'redux-miniprogram-bindings/dist/redux-miniprogram-bindings.alipay.min.js'
    
    setProvider({ store })
    
    // app.js
    // Make sure to call
    import './setupStore.js'
    
    App({
      /** ... */
    })
    
  3. Sử dụng trong page

    import { $page } from 'redux-miniprogram-bindings'
    import { actionCreator1, actionCreator2 } from 'your/store/action-creators/path'
    
    $page({
      mapState: ['dependent', 'state'],
      mapDispatch: {
        methodsName1: actionCreator1,
        methodsName2: actionCreator2,
      },
    })({
      onLoad() {
        // Read the value in state
        const { dependent } = this.data
        // dispatch actionCreator1
        this.methodsName1()
        // dispatch actionCreator2
        this.methodsName2(/** ...args */)
      },
    })
    
  4. Sử dụng trong component

    import { $component } from 'redux-miniprogram-bindings'
    import { actionCreator1, actionCreator2 } from 'your/store/action-creators/path'
    
    $component({
      mapState: [
        (state) => ({
          data1: state.dependent,
          data2: state.state,
        }),
      ],
      mapDispatch: (dispatch) => ({
        methodsName1: () => dispatch(actionCreator1()),
        methodsName2: (...args) => dispatch(actionCreator2(...args)),
      }),
    })({
      attached() {
        // Read the value in state
        const { data1: dependent } = this.data
        // dispatch actionCreator1
        this.methodsName1()
        // dispatch actionCreator2
        this.methodsName2(/** ...args */)
      },
    })
    
  5. Sử dụng trong XML

    <view tiki:if="{{ data1 }}">{{ data2 }}</view>
    
  6. Để biết thêm chi tiết, tham khảo API Ví dụ

API

setProvider:(config) => void

Thiết lập Provider

  • Các thuộc tính:

    • store:Store

      Redux Store instance object, phải được truyền.

    • namespace:string

      Mặc định có giá trị rỗng.

      Sau khi namespace được thiết lập, tất cả dependent state data sẽ được lưu trữ trong đối tượng với trường namespace là key value. Trong trường hợp này, giá trị state cần được đọc cùng với giá trị của trường namespace.

      Giả sử namespace: '$store', bạn cần sử dụng this.data.$store.xxx để nhận giá trị dependent state trong Page (hoặc Component) và bạn cần thêm tiền tố trong XML<view>{{ $store.xxx }}</view>

      Ý nghĩa của namespace:

      • Xác định dữ liệu nào đến từ store.
      • Dữ liệu trong store phải được dispatch để kích hoạt việc cập nhật. Namespace có thể giúp tránh dùng nhầm this.setData khiến dữ liệu trong store bị thay đổi không mong muốn nhờ có thêm tiền tố namespace:this.setData({ '$store.xxx': xxx })
    • component2: boolean

      Có mở component thứ 2 hay không,giá trị mặc định false. Chỉ có Alipay applet hỗ trợ.

  • Hàm này phải được gọi trước tất cả các code liên quan đến store

    Best Practices:

    Làm cách nào đảm bảo rằng setProvider được gọi trước tất cả mã sử dụng store

    Bạn có thể tạo một tập tin .js riêng biệt (ví dụ setupStore.js) để gọi setProvider,Import tập tin này ở đầu tập tin app.js.

    // setupStore.js
    import store from 'your/store/path'
    // WeChat Mini Program
    import { setProvider } from 'redux-miniprogram-bindings'
    
    setProvider({ store })
    
    // app.js
    import './setupStore.js'
    
    App({})
    

connect:(config) => (options) => void | options

Kết nối store

  • Thuộc tính:

    • type:"page" | "component"

      Đối tượng kết nối. Nhận giá trị:pagecomponent; mặc định là page.

    • mapState:(string | ((state: Object) => Object))[]

      State mà instance phụ thuộc vào,tuỳ chọn

      Dependent state data sẽ được inject vào dữ liệu của store, và sẽ được tự động cập nhật khi state biến đổi sau đó. Nó có kiểu dữ liệu là mảng; mảng này có thể chứa string hoặc hàm.

      • Mảng string:Các string là các key value tương ứng của dependent state. Page hay component sẽ tự động cập nhật trạng thái và đi vào hàng đợi theo lô chờ kích hoạt kết xuất view khi dependent state thay đổi.

        {
          // state1, state2 are the key values ​​in the state of the store
          // store.getState().state1
          // store.getState().state2
          mapState: ['state1', 'state2']
        }
        
      • Mảng hàm:Hàm nhận state làm tham số. Dữ liệu mới nhất của state có thể truy xuất thông qua đối tượng state. Hàm sẽ trả về một object; mỗi mục trong object đó có thể thuộc bất kỳ kiểu dữ liệu nào, thường dựa trên dữ liệu kết hợp state.

        Phương thức này thực thi hàm khi dữ liệu ở store thay đổi (không nhất thiết phải là state mà thực thể hiện tại phụ thuộc vào) và sau đó đối chiếu kết quả trả về của hàm với dữ liệu hiện có, và tiến hành kết xuất các thay đổi.

        {
          mapState: [
            (state) => ({
              region: state.province + state.city + state.area,
              name: state.userInfo.name,
            }),
          ]
        }
        
      • Mảng vừa string vừa hàm: hàm sẽ được gọi mỗi khi dữ liệu trong store thay đổi. Để tối ưu hoá hiệu suất hoạt động, tốt nhất bạn sử dụng càng nhiều string cũng như càng ít hàm càng tốt đối với mảng loại này. Ngoài ra, hàm nên nhỏ gọn và thực thi nhanh chóng.

        {
          mapState: [
            'name',
            (state) => ({
              region: state.province + state.city + state.area,
            }),
          ]
        }
        
    • mapDispatch:Object | dispatch => Object

      Inject một hàm xử lý dispatch, tuỳ chọn.

      • Ở dạng object:key là tên của hàm tuỳ biến, nó có thể được truy cập thông qua tên bên trong object, và value là giá trị của hàm actionCreator. actionCreator nằm trong hàm tự động gọi dispatch và được inject vào phương thức thực thể (instance method).

        // Configuration
        mapDispatch: {
          methodsName1: actionCreator1,
          methodsName2: actionCreator2,
        }
        
        // Call
        this.methodsName1()
        // equivalent to
        dispatch(actionCreator1())
        
        // call
        this.methodsName2(a, b, c)
        // equivalent to
        dispatch(methodsName2(a, b, c))
        
      • Ở dạng hàm: hàm này sẽ nhận dispatch làm tham số và trả ra một object, bao gồm hàm xử lý sau khi kết thúc tùy chỉnh.

        // Configuration
        mapDispatch: (dispatch) => ({
          methodsName1: () => dispatch(actionCreator1()),
          methodsName2: (...args) => dispatch(actionCreator2(...args)),
        })
        
        // Transfer
        this.methodsName1()
        this.methodsName2(a, b, c)
        

      Lưu ý: Những hàm bị inject thông qua thông qua mapDispatch cũng có thể được sử dụng như các hàm xử lý sự kiện trong XML. Nếu bạn cần truyền các tham số hàm, theo mặc định, phương thức xử lý sự kiện (event handler) sẽ truyền đối tượng sự kiện (event object) dưới dạng tham số đầu tiên của hàm.

      <view onTap="handleAdd">Add</view>
      
    • manual:boolean

      Cho phép gọi Page() hay Component(),giá trị mặc định false

      Nếu mang giá trị trueconnect trả về đối tượng tùy chọn đã được xử lý, bạn gọi thủ công Page() hay Component() để khai báo thực thể. Nó hữu ích cho người dùng trong việc tùy biến các mở rộng (extension).

      const options = connect({
        manual: true,
      })({
        // ...
      })
      
      options.xxx = 'xxx'
      
      Page(options)
      
  • Gói mở rộng:nếu bạn không thích dùng connect hay cách truyền tham số của nó, bạn có thể tự tạo ra gói cho riêng mình.

    Ví dụ về Page:

    // bootstrap.js
    import { connect } from 'redux-miniprogram-bindings'
    
    const oldPage = Page
    
    Page = function (options) {
      const { mapState, mapDispatch, ...restOptions } = options
    
      const realOptions = connect({
        mapState,
        mapDispatch,
        // This must be set to manual, because the Page function has been rewritten.
        manual: true,
      })(restOptions)
    
      oldPage(realOptions)
    }
    
    // app.js
    // Introduce extension
    import './bootstrap.js'
    
    App({})
    
    // use
    Page({
      mapState: [
        // ...
      ],
      mapDispatch: {
        // ...
      },
    })
    

$page

Alias used to connect Page

$page()({})
// equivalent to
connect({ type: 'page' })({})

$component

Alias used to connect Component

$component()({})
// equivalent to
connect({ type: 'component' })({})

Utils

useStore: () => Store

Lấy thực thể store

import { useStore } from 'redux-miniprogram-bindings'
const store = useStore()

// It is equivalent to the following method, but it is not recommended to use the following method when Alipay applet subcontracting, there will be multiple store instances
import store from 'your/store/path'

useState: () => Object

Lấy đối tượng state hiện tại.

import { useState } from 'redux-miniprogram-bindings'
const state = useState()

// It is equivalent to the following method, but it is not recommended to use the following method when Alipay applet subcontracting, there will be multiple store instances
import store from 'your/store/path'
const state = store.getState()

useDispatch: () => Dispatch

Lấy hàm dispatch

import { useDispatch } from 'redux-miniprogram-bindings'
const dispatch = useDispatch()

// It is equivalent to the following method, but it is not recommended to use the following method when Alipay applet subcontracting, there will be multiple store instances
import store from 'your/store/path'
const dispatch = store.dispatch

useSubscribe

Đăng ký (subscription) với store.

Nhận hàm callback khi dữ liệu trong store thay đổi. Hàm này nhận 2 tham số: currState - trạng hiện thời - và prevState - trạng thái trước đó. Nó sẽ so sánh 2 trạng thái này.

Trả về một hàm có thể được gọi để huỷ đăng ký.

import { useSubscribe } from 'redux-miniprogram-bindings'

$page()({
  onLoad() {
    // Enable subscription
    this.unsubscribe = useSubscribe((currState, prevState) => {
      if (currState.userInfo.name !== prevState.userInfo.name) {
        console.log('userName change')
      }
    })
  },

  onUnload() {
    // Huỷ đăng ký
    this.unsubscribe()
  },
})

useRef

Lấy tham chiếu đến dữ liệu trong đối tượng state.

Nhận hàm selector. Hàm selector này có tham số là state và có thể trả ra bất kỳ loại giá trị nào (khuyến nghị trả về assembled data sử dụng state).

Trả về đối tượng tham chiếu, có thuộc tính value chỉ đọc, thông qua đó giá trị mới nhất được hàm selector trả ra có thể được truy xuất.

import { useRef } from 'redux-miniprogram-bindings'
const selector = (state) => state.userInfo.name
const userNameRef = useRef(selector)

setInterval(() => {
  // Regardless of whether the state data is changed, the latest value of state.userInfo.name is always obtained
  console.log(userNameRef.value)
}, 1000)

Một hàm khác có chức năng tương tự

import { useState } from 'redux-miniprogram-bindings'
const selector = (state) => state.userInfo.name
const getUserName = () => selector(useState())

setInterval(() => {
  // Regardless of whether the state data is changed, the latest value of state.userInfo.name is always obtained
  console.log(getUserName())
}, 1000)

Việc sử dụng phương thức nào hoàn toàn phụ thuộc vào sở thích cá nhân.

useSelector

Lưu tạm (cache) kết quả của hàm selector.

Nhận state làm tham số và có thể trả ra bất cứ loại giá trị nào (khuyến nghị trả về assembled data sử dụng state).

Đồng thời, nó nhận mảng deps chứa key value của state mà sự thay đổi kết quả của hàm selector phụ thuộc vào.

Trả về một hàm có cùng signature với hàm selector. Mỗi khi hàm được thực thi, giá trị của mỗi phần trong mảng deps của dependency item sẽ được so sánh (shallow comparison) và hàm sẽ tái thực thi chỉ khi nào giá trị của dependency item thay đổi. Từ đó giảm tần suất thực thi các hàm luận lý phức tạp.

// Use with useRef
import { useRef, useSelector } from 'redux-miniprogram-bindings'
// Only re-execute when state.userInfo changes
const userNameselector = useSelector((state) => state.userInfo.name, ['userInfo'])
const userNameRef = useRef(userNameselector)

setInterval(() => {
  console.log(userNameRef.value)
}, 1000)
// Use with mapState
import { $page, useSelector } from 'redux-miniprogram-bindings'
// Only re-execute when state.userInfo changes
const userNameSelector = useSelector((state) => ({ userName: state.userInfo.name }), ['userInfo'])

$page({
  mapState: [userNameSelector],
})({})

Khác biệt (diff)

diff