Python과 브라우저 - 다시 보기

얼마 전에 파이톤을 사용하여 전방 웹 응용 프로그램을 개발하려고 시도한 최초의 정보를 발표했는데, 이 응용 프로그램은 Transcrypttranspiler를 사용하여React를 진행했다.


내가 처음 배운 일부 과정은 공식 코드React tutorial를 만드는 것이지만, 코드는 자바스크립트가 아닌 파이톤을 사용했다.내가 이렇게 할 때, 나는 강좌에서 사용하는 응용 프로그램의 구조를 매우 엄격하게 따랐다.그 이후로 저는 Transcrypt와React와 합작을 했고 React 응용 프로그램에서 깨끗한 Python 코드를 생성하기 위한 실천 기준을 형성했습니다.이 글은 제가 만든 프로그램의 원시적인 클래스 기반 버전 (이것은 React 튜토리얼의 기초) 을 사용하고 함수 구성 요소와 React 연결을 사용하는 것으로 전환했기 때문에, 그 실천을 보여 드리겠습니다. (이것은 제가 현재 사용하고 있는 전부입니다.)

개술


React 튜토리얼의 전제는 Tic-Tac-Toe 게임이다. 이 게임은 이동의 역사 기록을 유지하고 바둑판을 이전의 모든 것으로 리셋할 수 있다.이 디자인은 게임 구성 요소로 구성되어 있으며, 이 구성 요소는 게임 상태를 관리하고 이동 기록을 저장합니다.그런 다음 보드 렌더링을 처리하는 보드 어셈블리입니다.마지막으로, 게임에서 정사각형을 렌더링할 수 있는 정사각형 구성 요소가 하나 더 있습니다.
original version 애플리케이션에는

  • 색인html(응용프로그램 입구점 및 DOM 루트)

  • 게임?css (응용 프로그램의 css 선택기)

  • 티크타크가 반응했다.py(Python의 응용 프로그램 코드)

  • 파리아크.py(React.Component 클래스 및 기타 JavaScript 함수의 Python 패키지)
  • 지원 파일


    이 개조에 대해 CSS 파일과 색인을 사용합니다.html 파일은 기본적으로 변경되지 않습니다.
    목록 1: index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8"/>
        <title>Tic Tac React!</title>
        <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
        <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
        <link rel="stylesheet" href="game.css" />
    </head>
    <body>
    <div id="root"></div>
    <script type="module" src="__target__/tictacreact.js"></script>
    </body>
    </html>
    
    React 클래스 구성 요소를 더 이상 사용하지 않기 때문에pyreact를 정리할 수 있습니다.py 파일이 상당히 많습니다.이 Python 모듈을 사용하여 JavaScript에 대한 모든 맵을 저장한 다음 다른 Python 모듈에 맵을 가져올 수 있습니다.이 방법은 모든 자바스크립트의 혼란을 한 곳에 두는 데 도움이 되고, 모든 다른 Python 모듈을 순수한 Python으로 유지하는 데 도움이 된다. 이것은 Python linter를 기쁘게 한다.
    대부분의 프로젝트에 대해 나는 줄곧 패키지 묶음기를 사용하고 있는데, 그것은 Transcrypt plug-in 사용할 수 있는 것이 하나 있다.이런 것들이 있으면, 나는 보통 몇 줄의 글자를 행동 중이다.py 모듈은 다음과 같은 노드require() 함수를 사용하는 JavaScript ES5 스타일로 가져온 React 라이브러리를 로드합니다.
    React = require('react')
    ReactDOM = require('react-dom')
    
    이 예에서, 우리는 HTML 헤더에 React 라이브러리를 불러오기 때문에, React와 ReactDOM 이름 공간은 전역적일 것입니다. 그래서 나는 Python 파일에서 이 라이브러리를 삭제했을 뿐입니다.
    목록 2: pyreact.py
    # __pragma__ ('skip')
    """
    These JavaScript object stubs are just to
    quiet the Python linter and are ignored by transcrypt as long
    as they are imported inside of pragma skip/noskip lines.
    """
    
    class React:
        createElement = None
        useState = None
        useEffect = None
        createContext = None
        useContext = None
    
    
    class ReactDOM:
        render = None
    
    
    class document:
        getElementById = None
        addEventListener = None
    # __pragma__ ('noskip')
    
    
    # Map React javaScript objects to Python identifiers
    createElement = React.createElement
    useState = React.useState
    useEffect = React.useEffect
    createContext = React.createContext
    useContext = React.useContext
    
    
    # Wrap the ReactDOM.render method to hide JavaScript details
    def render(root_component, props, container):
        def main():
            ReactDOM.render(
                React.createElement(root_component, props),
                document.getElementById(container)
            )
    
        document.addEventListener("DOMContentLoaded", main)
    
    skip/noskip pragma 줄 사이의 부분은 사실 필요하지 않습니다. 다만, Python linter를 조용히 하기 위해서입니다. 자바스크립트의 대상 이름을 해석할 수 없기 때문입니다.컴파일러 명령 때문에 Transcrypt는 최종적으로 그것들을 무시할 뿐입니다.
    이 파일에 비친 것은 Transcrypt가 거대한 마력을 발휘하는 곳이다.나는 기본적으로 자바스크립트 대상을 파이톤 변수에 분배한다.그때부터 다른 Python 대상처럼 사용할 수 있습니다.이것은 다른 Python 모듈로 가져올 수 있으며, 방법을 호출할 수 있습니다.JavaScript 라이브러리를 사용하지만, 라이브러리의 API만 알면 파이톤으로 인코딩할 수 있습니다.render() 함수는 이전과 변화가 없고 ReactDOM.render() 방법의 포장기일 뿐입니다. 이 방법은 우리가 이에 따라 발생하는 자바스크립트를 봉인하여 호출할 수 있도록 합니다.

    재구성


    우리가 이 버전의 응용 프로그램에서 진행한 대부분의 실제 재구성은tictacreact에서 이루어진 것이다.py 모듈.클래스 구성 요소를 기능 구성 요소로 바꾸는 것 외에, 우리는 일부 상태의 업데이트 방식을 바꾸었다.비록 이것은 우리를 위해 많은 코드 줄을 절약하지는 않았지만, 지금은 이전보다 더욱 모듈화되고, 더욱 읽을 수 있기를 바란다.
    목록 3: tictacreact.py
    from pyreact import render, useState, createElement as el
    from pyreact import createContext, useContext
    
    
    Ctx = createContext()
    
    
    def Square(props):
        idx = props['idx']
    
        ctx = useContext(Ctx)
        squares = ctx['squares']
        onClick = ctx['onClick']
    
        return el('button', {'className': 'square',
                             'onClick': lambda: onClick(idx)
                             }, squares[idx])
    
    
    def Row(props):
        rowNum = props['rowNum']
    
        row = [el(Square, {'idx': (rowNum * 3) + col_num}) for col_num in range(3)]
        return el('div', {'className': 'board-row'}, row)
    
    
    def Board():
        rows = [el(Row, {'rowNum': row_num}) for row_num in range(3)]
        return el('div', None, rows)
    
    
    def Moves(props):
        numMoves = props['numMoves']
        setStepNumber = props['setStepNumber']
    
        def get_move(move):
            desc = ('Go to move #' + str(move)) if move > 0 else 'Go to game start'
            return el('li', {'key': move},
                      el('button', {'className': 'move-history',
                                    'onClick': lambda: setStepNumber(move)
                                    }, desc)
                      )
    
        return [get_move(move) for move in range(numMoves)]
    
    
    def Game():
        history, setHistory = useState([{'squares': [None for _ in range(9)]}])
        stepNumber, setStepNumber = useState(0)
    
        board = history[stepNumber]
        xIsNext = (stepNumber % 2) == 0
        winner = calculate_winner(board['squares'])
    
        if winner is not None:
            status = f"Winner: {winner}"
        elif stepNumber == 9:
            status = "No Winner"
        else:
            status = f"Next player: {'X' if xIsNext else 'O'}"
    
        def handle_click(i):
            new_squares = list(board['squares'])
            if winner or new_squares[i]:  # Already winner or square not empty
                return  # Nothing to do
    
            new_squares[i] = 'X' if xIsNext else 'O'
    
            tmp_history = history[:stepNumber + 1]  # Slice in case step changed
            new_history = [{'squares': move['squares']} for move in tmp_history]
            new_history.append({'squares': new_squares})
            setHistory(new_history)
            setStepNumber(len(new_history) - 1)
    
        return el(Ctx.Provider, {'value': {'squares': board['squares'],
                                           'onClick': handle_click}
                                 },
                  el('div', {'className': 'game'},
                     el('div', {'className': 'game-board'},
                        el(Board, None),
                        el('div', {'className': 'game-status'}, status),
                        ),
                     el('div', {'className': 'game-info'}, 'Move History',
                        el('ol', None,
                           el(Moves, {'numMoves': len(history),
                                      'setStepNumber': setStepNumber}
                              )
                           )
                        )
                     )
                  )
    
    
    # Render the component in a 'container' div
    render(Game, None, 'root')
    
    
    def calculate_winner(squares):
        lines = [
            [0, 1, 2],
            [3, 4, 5],
            [6, 7, 8],
            [0, 3, 6],
            [1, 4, 7],
            [2, 5, 8],
            [0, 4, 8],
            [2, 4, 6],
        ]
    
        for line in lines:
            a, b, c = line
            if squares[a] and (squares[a] == squares[b]) and (squares[a] == squares[c]):
                return squares[a]
        return None
    
    Game 구성 요소에서 우리는 클래스 기반state 대상을 단일useState() 갈고리로 바꾸는 변경을 했다.따라서 historystepNumber 상태 변수는 현재 자신의 부대 업데이트 함수를 가지고 있다.
    이전에 사용한 xIsNext 상태 변수는 다른 상태 변수의 값에 따라 계산된 것이기 때문에 나는 이를 국부 변수로 바꾸었다.기반 상태가 변경되어 다시 렌더링되면 다시 계산됩니다.
    주어진 시간에 표시되는 내용을 설명하기 위해서, 편의를 위해 현재 판 값을 저장하기 위해 국부 변수 board 를 추가했습니다.현재, stepNumber 의 결정에 따라, 우리는 이전처럼 매번 그것을 사용해야 하는 것이 아니라, 역사 기록 목록에서 한 번만 추출할 수 있다.다시 렌더링하면 값이 다시 계산됩니다.handle_click() 함수가 정리되었지만, 함수는 업데이트를 위해 복사historyboard 블록이 필요합니다. 왜냐하면 우리는 변할 수 없는 대상을 처리하고 있기 때문입니다.Transcrypt에서 Pythoncopy 표준 라이브러리를 지원하는 라이브러리에 추가하기 전에 자바스크립트 함수를 사용해야 합니다. history에서 목록 이해를 사용할 때처럼 수동으로 해야 합니다.
    new_history  = [{'squares': move['squares']} for  move  in  tmp_history]
    
    UI에 표시된 이전 이동 목록의 경우 Game 구성 요소에 목록 항목 요소를 생성하지 않고, 이 기능을 자체 Moves 구성 요소로 이동했습니다.이 변경 사항은 전체 응용 프로그램 구조를 읽을 수 있도록 Game 구성 요소를 정리했습니다.
    내가 시작한 실천은 props 대상의 값을 국부 변수로 해체하는 것이지 필요할 때 직접 props에서 그것들을 인용하는 것이 아니다.
    def Moves(props):
        numMoves = props['numMoves']
        setStepNumber = props['setStepNumber']
    
    이런 방법은 두 가지 일을 실현할 수 있다.우선, 해구 함수를 통해 맨 위의 모든 값을 정의함으로써, 나는 이 구성 요소가 어떤 도구를 필요로 하는지 정확히 알고 있으며, 전체 함수를 검색해서 그것을 찾을 필요가 없다.둘째, 이것은 함수의 코드를 정리했다. 함수에서 나는 실제로 이 값을 사용했기 때문에, 그 자리에서 사전을 찾을 필요가 없다.
    우리가 한 마지막 미묘한 변경은 handle_click() 함수와 board 제곱을 상하문 변수에 넣는 것이다.
    el(Ctx.Provider, {'value': {'squares': board['squares'],
                                'onClick': handle_click}
                                },
    
    이 상하문 변수를 사용하면 우리가 이 값을 다른 몇 개의 필요하지 않은 구성 요소 층으로 전달하는 것을 피할 수 있다. 그러면 우리는 Square 구성 요소에서 그것들을 사용할 수 있다.Board 구성 요소에서, 우리는 그것을 진정으로 정리했다. 왜냐하면 이것은 현재 세 개의 div 구성 요소를 포함하는 Row 요소만 되돌려주기 때문이다.우리가 현재 사용하고 있는 것은 상하문 변수이기 때문에, 우리는 더 이상 그것에 어떤 도구도 전달할 필요가 없다.Row 구성 요소는 우리가 이 재구성에 추가한 새로운 구성 요소로 개념적으로 생성된 내용을 설명한다.Board 구성 요소와 유사하며, Row 구성 요소가 되돌아오는 div 요소는 세 개Square 구성 요소만 포함합니다.Square 구성 요소는 이제 명령 함수가 아닌 진정한 React 구성 요소입니다.기능상 이전과 같지만, 우리는 확실히 ReactuseContext() 갈고리를 추가하여 우리가 여기에 사용해야 할 값을 추출했다.
    ctx = useContext(Ctx)
    squares = ctx['squares']
    onClick = ctx['onClick']
    
    마지막으로 우리는 초기 버전의 calculate_winner() 함수에 대해 약간의 최적화를 진행했다.

    전송 및 실행


    현재 Transcrypt 버전 3.7.16은 Python 3.6 또는 3.7에만 적용되므로 가상 환경을 설정할 때 다음과 같은 방법을 사용합니다.
    $ python3.7 -m venv venv
    그런 다음 활성화합니다.
    $ source ./venv/bin/activate(Windows용venv\Scripts\activate그런 다음 Transcrypt를 설치합니다.
    (venv)$pip install transcrypt애플리케이션을 구축하려면 Transcrypt 에 애플리케이션 입구점을 지정하면 다른 관련 모듈을 전송하기 위해 관계 트리를 반복합니다.
    (venv)$transcrypt --nomin --build --map tictacreact또한 다음과 같은 CLI 옵션도 제공합니다.

  • nomin - 생성된 자바스크립트를 축소하지 말라고 알려 줍니다. (주의: 자바가 실행될 때만 작동합니다.)

  • 빌드 - 처음부터 시작하도록 알려줍니다
  • .

  • map - JavaScript에서 Python으로 원본 코드 맵 파일을 생성하는 것을 알려 줍니다
  • 완성된 후에 웹 브라우저에서 프로그램을 열 수 있도록 생성된 파일을 제공해야 합니다.빠른 방법은 Python과 함께 제공되는 HTTP 서버를 사용하는 것입니다.
    (venv)$python -m http.server그런 다음 애플리케이션을 엽니다.
    http://localhost:8000/index.html
     

    이 응용 프로그램의 모든 소스 코드를 찾을 수 있습니다.
    https://github.com/JennaSys/tictacreact2
    또한 GitHub 페이지에는 소스 코드 맵이 있는 코드에 대한 라이브 데모도 제공됩니다.
    https://jennasys.github.io/tictacreact2/

    결론


    파이톤을 매우 좋아하고 자바스크립트를 별로 좋아하지 않는 사람으로서 지금까지 Transcrypt를 사용하여 파이톤으로 React 앱을 개발한 것은 저에게 효과가 좋았을 것입니다.제가 어떻게 이 점을 해냈는지 여러분과 공유하기 위해 파이썬 meetup팀에서 발표할 강연에 대한 요지를 제시하기 시작했습니다.사실이 증명하듯이, 이 요지가 줄곧 증가하고 있어서, 나는 마침내 그것에 관한 책 한 권을 썼다.만약 당신이 흥미가 있다면, "파이톤에 대한 반응"에 관한 책을 더 많이 찾을 수 있습니다: https://pyreact.com

    좋은 웹페이지 즐겨찾기